@coffeerunhobby/mcp-ha-connect 2.2.1 → 2.3.1

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 (281) hide show
  1. package/.env.example +1 -1
  2. package/README.md +11 -2
  3. package/dist/omadaClient/client.d.ts +6 -1
  4. package/dist/omadaClient/client.js +12 -1
  5. package/dist/omadaClient/client.js.map +1 -1
  6. package/dist/omadaClient/diagnose.d.ts +68 -0
  7. package/dist/omadaClient/diagnose.js +164 -0
  8. package/dist/omadaClient/diagnose.js.map +1 -0
  9. package/dist/omadaClient/index.d.ts +6 -0
  10. package/dist/omadaClient/index.js +15 -0
  11. package/dist/omadaClient/index.js.map +1 -1
  12. package/dist/omadaClient/macGroups.d.ts +58 -0
  13. package/dist/omadaClient/macGroups.js +124 -0
  14. package/dist/omadaClient/macGroups.js.map +1 -0
  15. package/dist/tools/infra/actions.d.ts +1 -1
  16. package/dist/tools/infra/actions.js +1 -1
  17. package/dist/tools/omada/index.js +6 -0
  18. package/dist/tools/omada/index.js.map +1 -1
  19. package/dist/tools/omada/macGroups.d.ts +19 -0
  20. package/dist/tools/omada/macGroups.js +77 -0
  21. package/dist/tools/omada/macGroups.js.map +1 -0
  22. package/dist/tools/omada/namespace.js +8 -0
  23. package/dist/tools/omada/namespace.js.map +1 -1
  24. package/dist/version.d.ts +1 -1
  25. package/dist/version.js +1 -1
  26. package/package.json +5 -1
  27. package/.codex/review-rules.md +0 -16
  28. package/.dockerignore +0 -8
  29. package/.gitattributes +0 -30
  30. package/.github/workflows/ci.yml +0 -193
  31. package/AGENTS.md +0 -389
  32. package/Dockerfile +0 -50
  33. package/docker-compose.yml +0 -23
  34. package/docs/CHANGELOG.md +0 -341
  35. package/docs/COVERAGE.md +0 -219
  36. package/docs/QUICK_START.md +0 -240
  37. package/eslint.config.js +0 -33
  38. package/n8n/ai-agent-safety-monitor.json +0 -355
  39. package/openapi.json +0 -1259
  40. package/scripts/check-version-sync.mjs +0 -69
  41. package/scripts/generate-jwt.ts +0 -121
  42. package/src/config.ts +0 -440
  43. package/src/haClient/automations.ts +0 -261
  44. package/src/haClient/calendars.ts +0 -94
  45. package/src/haClient/config.ts +0 -52
  46. package/src/haClient/devices.ts +0 -653
  47. package/src/haClient/entities.ts +0 -128
  48. package/src/haClient/events.ts +0 -689
  49. package/src/haClient/history.ts +0 -73
  50. package/src/haClient/index.ts +0 -336
  51. package/src/haClient/request.ts +0 -142
  52. package/src/haClient/services.ts +0 -45
  53. package/src/haClient/states.ts +0 -60
  54. package/src/haClient/updates.ts +0 -58
  55. package/src/haClient/wsCommand.ts +0 -44
  56. package/src/index.ts +0 -164
  57. package/src/localAI/index.ts +0 -88
  58. package/src/localAI/prompts.ts +0 -50
  59. package/src/localAI/providers/base.ts +0 -120
  60. package/src/localAI/providers/ollama.ts +0 -78
  61. package/src/localAI/providers/openai.ts +0 -86
  62. package/src/localAI/types.ts +0 -67
  63. package/src/omadaClient/auth.ts +0 -178
  64. package/src/omadaClient/client.ts +0 -337
  65. package/src/omadaClient/device.ts +0 -118
  66. package/src/omadaClient/index.ts +0 -310
  67. package/src/omadaClient/network.ts +0 -284
  68. package/src/omadaClient/request.ts +0 -365
  69. package/src/omadaClient/security.ts +0 -51
  70. package/src/omadaClient/site.ts +0 -344
  71. package/src/ownerQuestions/service.ts +0 -536
  72. package/src/permissions/index.ts +0 -143
  73. package/src/resources/index.ts +0 -213
  74. package/src/server/auth.ts +0 -175
  75. package/src/server/chatFace.ts +0 -789
  76. package/src/server/chatSlice.ts +0 -57
  77. package/src/server/common.ts +0 -170
  78. package/src/server/eventSubscription.ts +0 -343
  79. package/src/server/http.ts +0 -626
  80. package/src/server/instructions.ts +0 -97
  81. package/src/server/rateLimiter.ts +0 -227
  82. package/src/server/stdio.ts +0 -41
  83. package/src/server/stream.ts +0 -205
  84. package/src/tools/ai/analyzeSensors.ts +0 -33
  85. package/src/tools/ai/index.ts +0 -20
  86. package/src/tools/common.ts +0 -442
  87. package/src/tools/homeassistant/askOwner.ts +0 -133
  88. package/src/tools/homeassistant/automationControls.ts +0 -66
  89. package/src/tools/homeassistant/calendar.ts +0 -97
  90. package/src/tools/homeassistant/callService.ts +0 -25
  91. package/src/tools/homeassistant/controlClimate.ts +0 -68
  92. package/src/tools/homeassistant/controlCover.ts +0 -48
  93. package/src/tools/homeassistant/controlFan.ts +0 -44
  94. package/src/tools/homeassistant/controlLight.ts +0 -38
  95. package/src/tools/homeassistant/controlMediaPlayer.ts +0 -50
  96. package/src/tools/homeassistant/createAutomation.ts +0 -35
  97. package/src/tools/homeassistant/deleteAutomation.ts +0 -24
  98. package/src/tools/homeassistant/entityAction.ts +0 -35
  99. package/src/tools/homeassistant/entityGuard.ts +0 -28
  100. package/src/tools/homeassistant/getAllSensors.ts +0 -46
  101. package/src/tools/homeassistant/getAutomationTrace.ts +0 -30
  102. package/src/tools/homeassistant/getDomainSummary.ts +0 -29
  103. package/src/tools/homeassistant/getEntitiesByDomain.ts +0 -56
  104. package/src/tools/homeassistant/getHistory.ts +0 -24
  105. package/src/tools/homeassistant/getState.ts +0 -24
  106. package/src/tools/homeassistant/getStates.ts +0 -46
  107. package/src/tools/homeassistant/getVersion.ts +0 -21
  108. package/src/tools/homeassistant/index.ts +0 -121
  109. package/src/tools/homeassistant/listAutomations.ts +0 -35
  110. package/src/tools/homeassistant/listEntities.ts +0 -44
  111. package/src/tools/homeassistant/listPersons.ts +0 -46
  112. package/src/tools/homeassistant/sceneAndScript.ts +0 -53
  113. package/src/tools/homeassistant/searchEntities.ts +0 -58
  114. package/src/tools/homeassistant/sendNotification.ts +0 -179
  115. package/src/tools/homeassistant/systemTools.ts +0 -52
  116. package/src/tools/homeassistant/triggerAutomation.ts +0 -24
  117. package/src/tools/index.ts +0 -78
  118. package/src/tools/infra/actions.ts +0 -95
  119. package/src/tools/infra/index.ts +0 -138
  120. package/src/tools/omada/blockClient.ts +0 -28
  121. package/src/tools/omada/cyclePoePort.ts +0 -32
  122. package/src/tools/omada/deleteClient.ts +0 -27
  123. package/src/tools/omada/disableClientRateLimit.ts +0 -23
  124. package/src/tools/omada/getClient.ts +0 -14
  125. package/src/tools/omada/getDevice.ts +0 -14
  126. package/src/tools/omada/getFirewallSetting.ts +0 -14
  127. package/src/tools/omada/getInternetInfo.ts +0 -14
  128. package/src/tools/omada/getLanNetworkList.ts +0 -14
  129. package/src/tools/omada/getLanProfileList.ts +0 -14
  130. package/src/tools/omada/getPortForwardingStatus.ts +0 -26
  131. package/src/tools/omada/getRateLimitProfiles.ts +0 -14
  132. package/src/tools/omada/getSiteNtpStatus.ts +0 -14
  133. package/src/tools/omada/getSsidDetail.ts +0 -22
  134. package/src/tools/omada/getSsidList.ts +0 -21
  135. package/src/tools/omada/getSwitchStackDetail.ts +0 -14
  136. package/src/tools/omada/getThreatList.ts +0 -42
  137. package/src/tools/omada/getWlanGroupList.ts +0 -14
  138. package/src/tools/omada/graph.ts +0 -187
  139. package/src/tools/omada/index.ts +0 -153
  140. package/src/tools/omada/listBlockedClients.ts +0 -17
  141. package/src/tools/omada/listClients.ts +0 -14
  142. package/src/tools/omada/listClientsActivity.ts +0 -25
  143. package/src/tools/omada/listClientsPastConnections.ts +0 -44
  144. package/src/tools/omada/listDevices.ts +0 -14
  145. package/src/tools/omada/listDevicesStats.ts +0 -41
  146. package/src/tools/omada/listMostActiveClients.ts +0 -14
  147. package/src/tools/omada/listSites.ts +0 -18
  148. package/src/tools/omada/namespace.ts +0 -1164
  149. package/src/tools/omada/searchDevices.ts +0 -20
  150. package/src/tools/omada/setClientRateLimit.ts +0 -26
  151. package/src/tools/omada/setClientRateLimitProfile.ts +0 -25
  152. package/src/tools/omada/setSiteNtpServers.ts +0 -36
  153. package/src/tools/omada/setSsidEnabled.ts +0 -38
  154. package/src/tools/omada/unblockClient.ts +0 -26
  155. package/src/tools/types.ts +0 -11
  156. package/src/tools/utils/pagination-schema.ts +0 -27
  157. package/src/types/ai/index.ts +0 -11
  158. package/src/types/ai/types.ts +0 -67
  159. package/src/types/homeassistant/automations.ts +0 -43
  160. package/src/types/homeassistant/calendars.ts +0 -26
  161. package/src/types/homeassistant/config.ts +0 -14
  162. package/src/types/homeassistant/entities.ts +0 -14
  163. package/src/types/homeassistant/errors.ts +0 -33
  164. package/src/types/homeassistant/haConfig.ts +0 -7
  165. package/src/types/homeassistant/history.ts +0 -13
  166. package/src/types/homeassistant/index.ts +0 -46
  167. package/src/types/homeassistant/request.ts +0 -6
  168. package/src/types/homeassistant/serviceCall.ts +0 -18
  169. package/src/types/homeassistant/states.ts +0 -58
  170. package/src/types/homeassistant/updates.ts +0 -28
  171. package/src/types/index.ts +0 -12
  172. package/src/types/omada/activeClientInfo.ts +0 -17
  173. package/src/types/omada/clientActivity.ts +0 -32
  174. package/src/types/omada/clientBlockStatus.ts +0 -11
  175. package/src/types/omada/clientPastConnection.ts +0 -82
  176. package/src/types/omada/index.ts +0 -14
  177. package/src/types/omada/omadaApiResponse.ts +0 -5
  178. package/src/types/omada/omadaClientInfo.ts +0 -11
  179. package/src/types/omada/omadaDeviceInfo.ts +0 -11
  180. package/src/types/omada/omadaDeviceStats.ts +0 -31
  181. package/src/types/omada/omadaSiteSummary.ts +0 -5
  182. package/src/types/omada/oswStackDetail.ts +0 -11
  183. package/src/types/omada/paginatedResult.ts +0 -6
  184. package/src/types/omada/rateLimitProfile.ts +0 -58
  185. package/src/types/omada/threatInfo.ts +0 -38
  186. package/src/types/omada/tokenResult.ts +0 -6
  187. package/src/types.ts +0 -2
  188. package/src/utils/config-validations.ts +0 -68
  189. package/src/utils/jwt.ts +0 -112
  190. package/src/utils/logger.ts +0 -121
  191. package/src/utils/plainText.ts +0 -48
  192. package/src/utils/redact.ts +0 -59
  193. package/src/utils/sanitizeError.ts +0 -18
  194. package/src/utils/tlsDispatcher.ts +0 -69
  195. package/src/version.ts +0 -5
  196. package/tests/bdd/automation-creation.spec.ts +0 -214
  197. package/tests/bdd/automation-management.spec.ts +0 -267
  198. package/tests/bdd/calendar.spec.ts +0 -190
  199. package/tests/bdd/configuration.spec.ts +0 -283
  200. package/tests/bdd/device-control.spec.ts +0 -344
  201. package/tests/bdd/entity-listing.spec.ts +0 -208
  202. package/tests/bdd/entity-search.spec.ts +0 -183
  203. package/tests/bdd/history-retrieval.spec.ts +0 -195
  204. package/tests/bdd/notifications.spec.ts +0 -238
  205. package/tests/bdd/service-calls.spec.ts +0 -144
  206. package/tests/bdd/state-retrieval.spec.ts +0 -204
  207. package/tests/bench/hotpaths.bench.ts +0 -68
  208. package/tests/config.test.ts +0 -355
  209. package/tests/features/automation-creation.feature +0 -36
  210. package/tests/features/automation-management.feature +0 -54
  211. package/tests/features/calendar.feature +0 -37
  212. package/tests/features/configuration.feature +0 -70
  213. package/tests/features/device-control.feature +0 -100
  214. package/tests/features/entity-listing.feature +0 -51
  215. package/tests/features/entity-search.feature +0 -44
  216. package/tests/features/history-retrieval.feature +0 -38
  217. package/tests/features/notifications.feature +0 -37
  218. package/tests/features/service-calls.feature +0 -35
  219. package/tests/features/state-retrieval.feature +0 -37
  220. package/tests/fixtures/openapi-default-slice.json +0 -290
  221. package/tests/haClient/automationTrace.test.ts +0 -59
  222. package/tests/haClient/automations.test.ts +0 -149
  223. package/tests/haClient/config.test.ts +0 -117
  224. package/tests/haClient/entities.test.ts +0 -188
  225. package/tests/haClient/events.test.ts +0 -455
  226. package/tests/haClient/eventsTls.test.ts +0 -52
  227. package/tests/haClient/history.test.ts +0 -228
  228. package/tests/haClient/index.test.ts +0 -243
  229. package/tests/haClient/request.test.ts +0 -217
  230. package/tests/haClient/services.test.ts +0 -120
  231. package/tests/haClient/states.test.ts +0 -119
  232. package/tests/haClient/updates.test.ts +0 -193
  233. package/tests/index.test.ts +0 -160
  234. package/tests/integration/homeassistant.test.ts +0 -144
  235. package/tests/integration/omada.test.ts +0 -167
  236. package/tests/integration/server.test.ts +0 -151
  237. package/tests/localAI/index.test.ts +0 -177
  238. package/tests/localAI/ollama.test.ts +0 -187
  239. package/tests/localAI/openai.test.ts +0 -257
  240. package/tests/omadaClient/actions.test.ts +0 -161
  241. package/tests/omadaClient/clientBlocks.test.ts +0 -170
  242. package/tests/omadaClient/ntp.test.ts +0 -381
  243. package/tests/omadaClient/site.test.ts +0 -37
  244. package/tests/ownerQuestions/service.test.ts +0 -499
  245. package/tests/ownerQuestions/tools.test.ts +0 -186
  246. package/tests/permissions/index.test.ts +0 -217
  247. package/tests/resources/index.test.ts +0 -288
  248. package/tests/scripts/generateJwt.test.ts +0 -44
  249. package/tests/security/accessControl.test.ts +0 -223
  250. package/tests/security/authentication.test.ts +0 -117
  251. package/tests/security/configuration.test.ts +0 -170
  252. package/tests/security/cryptoTransport.test.ts +0 -172
  253. package/tests/security/dependencies.test.ts +0 -152
  254. package/tests/security/injection.test.ts +0 -117
  255. package/tests/security/insecureDesign.test.ts +0 -121
  256. package/tests/security/logging.test.ts +0 -131
  257. package/tests/security/resourceConsumption.test.ts +0 -142
  258. package/tests/server/auth.test.ts +0 -249
  259. package/tests/server/chatFace.test.ts +0 -320
  260. package/tests/server/common.test.ts +0 -84
  261. package/tests/server/http.test.ts +0 -561
  262. package/tests/server/instructions.test.ts +0 -152
  263. package/tests/server/memoizeJsonSchema.test.ts +0 -71
  264. package/tests/server/stdio.test.ts +0 -99
  265. package/tests/server/stream.test.ts +0 -285
  266. package/tests/server/toolVisibility.http.test.ts +0 -111
  267. package/tests/server/toolVisibility.test.ts +0 -129
  268. package/tests/tools/ai/analyzeSensors.test.ts +0 -227
  269. package/tests/tools/common.test.ts +0 -123
  270. package/tests/tools/homeassistant/handlers.test.ts +0 -872
  271. package/tests/tools/infra/invokeAction.test.ts +0 -280
  272. package/tests/tools/omada/graph.test.ts +0 -385
  273. package/tests/tools/omada/handlers.test.ts +0 -897
  274. package/tests/tools/omada/namespace.test.ts +0 -483
  275. package/tests/tools/registry.test.ts +0 -168
  276. package/tests/utils/config-validations.test.ts +0 -352
  277. package/tests/utils/jwt.test.ts +0 -115
  278. package/tests/utils/logger.test.ts +0 -327
  279. package/tests/utils/redact.test.ts +0 -105
  280. package/tsconfig.json +0 -24
  281. package/vitest.config.ts +0 -38
@@ -1,193 +0,0 @@
1
- name: CI
2
-
3
- # Advisory only. This workflow REPORTS a pass/fail status on commits and PRs.
4
- # It does NOT block commits, pushes, or merges. The code builds, publishes, and
5
- # deploys with zero dependency on this file — delete it and nothing breaks.
6
- # (Blocking only happens if you separately enable branch-protection in the
7
- # GitHub web UI, which is off by default and intentionally not set here.)
8
-
9
- on:
10
- push:
11
- branches: [main, dev]
12
- # Version tags trigger the full gate + auto-create the GitHub Release (see
13
- # the `release` job) — v1.5.4 sat tag-only for two weeks because the manual
14
- # `gh release create` step kept getting skipped.
15
- tags: ['v*']
16
- pull_request:
17
- branches: [main, dev]
18
- # Lets you re-run it by hand from the Actions tab.
19
- workflow_dispatch:
20
-
21
- # If you push twice quickly, cancel the older run instead of stacking them.
22
- concurrency:
23
- group: ci-${{ github.ref }}
24
- cancel-in-progress: true
25
-
26
- jobs:
27
- build-test-lint:
28
- runs-on: ubuntu-latest
29
-
30
- strategy:
31
- # Don't kill the Node 22 run just because Node 20 failed (or vice-versa) —
32
- # we want to see both results.
33
- fail-fast: false
34
- matrix:
35
- # The LTS lines we support (package.json engines: ">=20"). 24.x is the
36
- # Docker image's actual runtime (since v1.5.6); 26 is the next LTS (2026-10).
37
- node-version: ['20.x', '22.x', '24.x', '26.x']
38
-
39
- name: node ${{ matrix.node-version }}
40
-
41
- steps:
42
- - name: Checkout
43
- uses: actions/checkout@v7
44
-
45
- - name: Set up Node ${{ matrix.node-version }}
46
- uses: actions/setup-node@v7
47
- with:
48
- node-version: ${{ matrix.node-version }}
49
- cache: npm
50
-
51
- # Install deps from the lockfile (reproducible; same command Docker uses; never
52
- # edits the lockfile). RECURRING TRAP: `npm ci` fails here if the lockfile was
53
- # regenerated on Windows — that OMITS linux-only optional deps (@emnapi/*) and
54
- # the Linux install rejects it. FIX: regenerate the lockfile on Linux / in the
55
- # node:24-alpine build image (docker run … npm install --package-lock-only),
56
- # never `npm install` on Windows.
57
- - name: Install dependencies (npm ci — fails on a Windows-drifted lockfile)
58
- run: npm ci
59
-
60
- # 1. Compile cleanly (tsc -> dist/). Catches type errors.
61
- - name: Build
62
- run: npm run build
63
-
64
- # 2. Lint src + tests. CLAUDE.md policy: zero errors AND zero warnings.
65
- # --max-warnings=0 makes a warning fail the job, matching that policy.
66
- - name: Lint
67
- run: npm run lint -- --max-warnings=0
68
-
69
- # 3. Version-sync gate. Fails if package.json / src/version.ts / openapi.json
70
- # disagree on the version — the exact drift CLAUDE.md warns about.
71
- - name: Version sync
72
- run: npm run check:sync
73
-
74
- # 4. Unit tests WITH coverage gate. test:coverage excludes tests/integration/**
75
- # (those need a live .env CI must never have) and enforces the regression
76
- # floor in vitest.config.ts — a coverage DROP fails the build.
77
- - name: Test + coverage gate
78
- run: npm run test:coverage
79
-
80
- docker-smoke:
81
- # Boot-test the ACTUAL image: build it, point it at a mock Home Assistant,
82
- # and require /health to answer. Motivation: v1.5.5 shipped without ever
83
- # having been run — its first boot was in production on the NAS (where the
84
- # node:26 base then failed its first fetch). A release must never again be
85
- # the image's first boot.
86
- runs-on: ubuntu-latest
87
- steps:
88
- - name: Checkout
89
- uses: actions/checkout@v7
90
-
91
- - name: Build image
92
- run: docker build -t mcp-smoke:ci .
93
-
94
- - name: Boot against a mock HA and assert /health
95
- run: |
96
- set -eu
97
- docker network create smoke
98
- # Mock HA: 200 {"message":"API running."} — the same body the real
99
- # /api/ startup check (checkApi) expects.
100
- docker run -d --name mockha --network smoke node:24-alpine \
101
- node -e 'require("http").createServer((q,s)=>{s.writeHead(200,{"Content-Type":"application/json"});s.end(JSON.stringify({message:"API running."}))}).listen(8123,()=>console.log("mock HA up"))'
102
- # HA_STRICT_SSL=false mirrors the REAL NAS config — the exact combination
103
- # (http HA + strictSsl=false) that crash-looped v1.5.5 on a node:26 base
104
- # (npm-undici-6 dispatcher passed to Node's built-in fetch -> "invalid
105
- # onError method" -> "fetch failed"). The smoke must boot with it forever.
106
- docker run -d --name mcp --network smoke \
107
- -e MCP_SERVER_USE_HTTP=true \
108
- -e MCP_HTTP_PORT=3000 -e MCP_HTTP_BIND_ADDR=0.0.0.0 \
109
- -e MCP_HTTP_ENABLE_HEALTHCHECK=true -e MCP_HTTP_HEALTHCHECK_PATH=/health \
110
- -e MCP_AUTH_METHOD=bearer -e MCP_AUTH_SECRET=ci-smoke-dummy-secret-0123456789 \
111
- -e HA_PLUGIN_ENABLED=true -e HA_URL=http://mockha:8123 -e HA_TOKEN=ci-dummy \
112
- -e HA_STRICT_SSL=false \
113
- -e AI_PLUGIN_ENABLED=false -e OMADA_PLUGIN_ENABLED=false \
114
- mcp-smoke:ci
115
- for i in $(seq 1 15); do
116
- if docker run --rm --network smoke curlimages/curl:latest -sf http://mcp:3000/health >/dev/null 2>&1; then
117
- echo "smoke OK: image boots, startup HA check passes, /health answers"
118
- exit 0
119
- fi
120
- sleep 2
121
- done
122
- echo "SMOKE FAILED — container logs:"
123
- docker logs mcp
124
- exit 1
125
-
126
- publish-npm:
127
- # Tokenless npm publish via OIDC trusted publishing (one-time npmjs.com config:
128
- # package Settings -> Trusted Publisher -> GitHub Actions, this repo + ci.yml).
129
- # Replaces the bypass-2FA granular token: npm is restricting those from Aug 2026
130
- # and banning direct publish ~Jan 2027 (github.blog changelog 2026-07-08), and
131
- # ours expires 2026-09-12 anyway. --provenance attests the package to this run.
132
- # dist/ is GITIGNORED (built, not committed) — this job MUST npm ci + build, or
133
- # the published tarball ships without the compiled code.
134
- if: startsWith(github.ref, 'refs/tags/v')
135
- needs: [build-test-lint, docker-smoke]
136
- runs-on: ubuntu-latest
137
- permissions:
138
- contents: read
139
- id-token: write # the OIDC credential — this IS the auth, no secret anywhere
140
- steps:
141
- - name: Checkout
142
- uses: actions/checkout@v7
143
-
144
- - name: Set up Node
145
- uses: actions/setup-node@v7
146
- with:
147
- node-version: '24.x'
148
- registry-url: 'https://registry.npmjs.org'
149
-
150
- - name: Install + build (dist/ is gitignored — the tarball needs it compiled)
151
- run: |
152
- npm ci
153
- npm run build
154
- test -f dist/index.js || { echo "FATAL: build produced no dist/index.js"; exit 1; }
155
-
156
- - name: Publish (tag must match package version; idempotent if already published)
157
- run: |
158
- set -eu
159
- PKG=$(node -p "require('./package.json').name")
160
- VER=$(node -p "require('./package.json').version")
161
- if [ "v$VER" != "$GITHUB_REF_NAME" ]; then
162
- echo "FATAL: tag $GITHUB_REF_NAME != package.json version $VER (check:sync covers files, this covers the tag)"
163
- exit 1
164
- fi
165
- if npm view "$PKG@$VER" version >/dev/null 2>&1; then
166
- echo "$PKG@$VER already on the registry — skipping (idempotent)"
167
- else
168
- npm publish --provenance --access public
169
- fi
170
- # Belt-and-braces: the tarball npm just packed must contain the entrypoint.
171
- npm pack --dry-run 2>&1 | grep -q 'dist/index.js' || { echo "FATAL: packed tarball missing dist/index.js"; exit 1; }
172
-
173
- release:
174
- # Auto-create the GitHub Release when a `v*` tag passes BOTH gates above —
175
- # the test matrix AND the image boot-test. Notes are auto-generated from the
176
- # commits since the previous release; polish them in the web UI afterwards
177
- # if the release deserves prose.
178
- # Idempotent: if the Release already exists (created by hand), this no-ops.
179
- if: startsWith(github.ref, 'refs/tags/v')
180
- needs: [build-test-lint, docker-smoke]
181
- runs-on: ubuntu-latest
182
- permissions:
183
- contents: write
184
- steps:
185
- - name: Checkout
186
- uses: actions/checkout@v7
187
-
188
- - name: Create GitHub Release
189
- env:
190
- GH_TOKEN: ${{ github.token }}
191
- run: |
192
- gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 || \
193
- gh release create "$GITHUB_REF_NAME" --latest --generate-notes --title "$GITHUB_REF_NAME"
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