@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
@@ -1,69 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * check-version-sync — fails if the version string has drifted across the files
4
- * that must agree. CLAUDE.md calls this out explicitly: openapi.json's version
5
- * "drifts easily" on release. This is the durable gate that makes the drift loud
6
- * instead of silent.
7
- *
8
- * Sources of truth that MUST match:
9
- * - package.json -> .version
10
- * - src/version.ts -> export const VERSION = '...'
11
- * - openapi.json -> .info.version
12
- *
13
- * Exit 0 when all agree; exit 1 with a diff when they don't. No dependencies,
14
- * no build step — plain Node ESM so CI can run it with `node`.
15
- */
16
- import { readFileSync } from 'node:fs';
17
- import { fileURLToPath } from 'node:url';
18
- import { dirname, join } from 'node:path';
19
-
20
- const root = join(dirname(fileURLToPath(import.meta.url)), '..');
21
-
22
- /** Read a file, or throw a clear error naming the missing path. */
23
- function read(rel) {
24
- try {
25
- return readFileSync(join(root, rel), 'utf8');
26
- } catch (e) {
27
- throw new Error(`Cannot read ${rel}: ${e.message}`);
28
- }
29
- }
30
-
31
- const sources = [];
32
-
33
- // package.json -> .version
34
- {
35
- const pkg = JSON.parse(read('package.json'));
36
- sources.push({ file: 'package.json', version: pkg.version });
37
- }
38
-
39
- // src/version.ts -> export const VERSION = 'X.Y.Z'
40
- {
41
- const txt = read('src/version.ts');
42
- const m = txt.match(/export\s+const\s+VERSION\s*=\s*['"]([^'"]+)['"]/);
43
- sources.push({ file: 'src/version.ts', version: m ? m[1] : undefined });
44
- }
45
-
46
- // openapi.json -> .info.version
47
- {
48
- const spec = JSON.parse(read('openapi.json'));
49
- sources.push({ file: 'openapi.json', version: spec?.info?.version });
50
- }
51
-
52
- const missing = sources.filter((s) => !s.version);
53
- if (missing.length > 0) {
54
- console.error('✖ version-sync: could not extract a version from:');
55
- for (const m of missing) console.error(` - ${m.file}`);
56
- process.exit(1);
57
- }
58
-
59
- const unique = [...new Set(sources.map((s) => s.version))];
60
- if (unique.length > 1) {
61
- console.error('✖ version-sync: version strings disagree across files:');
62
- for (const s of sources) console.error(` ${s.version.padEnd(12)} ${s.file}`);
63
- console.error('\n Fix: set all three to the same version before releasing.');
64
- console.error(' (package.json, src/version.ts, openapi.json — see CLAUDE.md Release Process.)');
65
- process.exit(1);
66
- }
67
-
68
- console.log(`✓ version-sync: all sources agree on ${unique[0]}`);
69
- process.exit(0);
@@ -1,121 +0,0 @@
1
- #!/usr/bin/env tsx
2
- /**
3
- * Generate JWT tokens for MCP server authentication
4
- *
5
- * Usage:
6
- * npx tsx scripts/generate-jwt.ts [options]
7
- * npm run generate:jwt -- [options]
8
- *
9
- * Options:
10
- * --sub <user> Subject (username), default: 'admin'
11
- * --exp <duration> Expiration (e.g., '1h', '7d', '30d'), default: '7d'
12
- * --secret <secret> JWT secret (or use MCP_AUTH_SECRET env var)
13
- *
14
- * Examples:
15
- * npm run generate:jwt
16
- * npm run generate:jwt -- --sub claude --exp 30d
17
- * npm run generate:jwt -- --sub readonly --exp 1h --secret mysecret
18
- */
19
-
20
- import { createJwt } from '../src/utils/jwt.js';
21
-
22
- function parseArgs(): { sub: string; exp: string; secret: string } {
23
- const args = process.argv.slice(2);
24
- let sub = 'admin';
25
- let exp = '7d';
26
- let secret = process.env.MCP_AUTH_SECRET || '';
27
-
28
- for (let i = 0; i < args.length; i++) {
29
- if (args[i] === '--sub' && args[i + 1]) {
30
- sub = args[++i];
31
- } else if (args[i] === '--exp' && args[i + 1]) {
32
- exp = args[++i];
33
- } else if (args[i] === '--secret' && args[i + 1]) {
34
- secret = args[++i];
35
- } else if (args[i] === '--help' || args[i] === '-h') {
36
- console.log(`
37
- Generate JWT tokens for MCP server authentication
38
-
39
- Usage:
40
- npm run generate:jwt -- [options]
41
-
42
- Options:
43
- --sub <user> Subject (username), default: 'admin'
44
- --exp <duration> Expiration (e.g., '1h', '7d', '30d'), default: '7d'
45
- --secret <secret> JWT secret (or use MCP_AUTH_SECRET env var)
46
-
47
- Examples:
48
- npm run generate:jwt
49
- npm run generate:jwt -- --sub claude --exp 30d
50
- npm run generate:jwt -- --sub readonly --exp 1h
51
- `);
52
- process.exit(0);
53
- }
54
- }
55
-
56
- return { sub, exp, secret };
57
- }
58
-
59
- function parseDuration(duration: string): number {
60
- const match = duration.match(/^(\d+)([smhd])$/);
61
- if (!match) {
62
- throw new Error(`Invalid duration: ${duration}. Use format like '1h', '7d', '30d'`);
63
- }
64
-
65
- const value = parseInt(match[1], 10);
66
- const unit = match[2];
67
-
68
- const now = Math.floor(Date.now() / 1000);
69
- switch (unit) {
70
- case 's':
71
- return now + value;
72
- case 'm':
73
- return now + value * 60;
74
- case 'h':
75
- return now + value * 3600;
76
- case 'd':
77
- return now + value * 86400;
78
- default:
79
- throw new Error(`Unknown unit: ${unit}`);
80
- }
81
- }
82
-
83
- function main() {
84
- const { sub, exp, secret } = parseArgs();
85
-
86
- if (!secret) {
87
- console.error('Error: No secret provided.');
88
- console.error('Set MCP_AUTH_SECRET environment variable or use --secret option.');
89
- console.error('\nExample:');
90
- console.error(' export MCP_AUTH_SECRET=your-secret-here');
91
- console.error(' npm run generate:jwt');
92
- process.exit(1);
93
- }
94
-
95
- if (secret.length < 32) {
96
- console.error('Warning: Secret should be at least 32 characters for security.');
97
- }
98
-
99
- const iat = Math.floor(Date.now() / 1000);
100
- const expTime = parseDuration(exp);
101
-
102
- const payload = {
103
- sub,
104
- iat,
105
- exp: expTime,
106
- };
107
-
108
- const token = createJwt(payload, secret);
109
-
110
- // Details go to stderr; stdout carries ONLY the bare token, exactly once, so
111
- // `TOKEN=$(npm run -s generate:jwt -- ...)` captures one usable token.
112
- console.error('\n=== JWT Token Generated ===\n');
113
- console.error('Subject:', sub);
114
- console.error('Issued at:', new Date(iat * 1000).toISOString());
115
- console.error('Expires at:', new Date(expTime * 1000).toISOString());
116
- console.error('Duration:', exp);
117
- console.error('\nUsage: curl -H "Authorization: Bearer <token>" http://localhost:3000/mcp ...\n');
118
- console.log(token);
119
- }
120
-
121
- main();
package/src/config.ts DELETED
@@ -1,440 +0,0 @@
1
- import { z } from 'zod';
2
- import { isValidBindAddress, isValidOrigin, isLoopbackAddress } from './utils/config-validations.js';
3
- import { parsePermissionsConfig, type PermissionsConfig } from './permissions/index.js';
4
- import { parseRestActions, type RestAction } from './tools/infra/actions.js';
5
- import { parseChatTools, type ChatToolsSlice } from './server/chatSlice.js';
6
- import { logger } from './utils/logger.js';
7
- import type { AIProviderType } from './localAI/types.js';
8
-
9
- const createBooleanStringSchema = (defaultValue: boolean) =>
10
- z
11
- .union([z.literal('true'), z.literal('false')])
12
- .optional()
13
- .transform((value: 'true' | 'false' | undefined) => {
14
- if (value === undefined) return defaultValue;
15
- return value === 'true';
16
- });
17
-
18
- const numericStringSchema = z
19
- .string()
20
- .optional()
21
- .transform((value: string | undefined) => (value ? Number.parseInt(value, 10) : undefined))
22
- .pipe(z.number().positive().optional());
23
-
24
- const envSchema = z
25
- .object({
26
- // Plugin Enable Flags (default: false)
27
- haPluginEnabled: createBooleanStringSchema(false),
28
- aiPluginEnabled: createBooleanStringSchema(false),
29
- omadaPluginEnabled: createBooleanStringSchema(false),
30
-
31
- // Home Assistant Client Configuration (optional if using Omada only)
32
- haUrl: z.string().url({ message: 'HA_URL must be a valid URL' }).optional(),
33
- haToken: z.string().min(1).optional(),
34
- haStrictSsl: createBooleanStringSchema(true),
35
- haTimeout: numericStringSchema,
36
-
37
- // Omada Client Configuration (optional)
38
- omadaBaseUrl: z.string().url({ message: 'OMADA_BASE_URL must be a valid URL' }).optional(),
39
- omadaClientId: z.string().min(1).optional(),
40
- omadaClientSecret: z.string().min(1).optional(),
41
- omadaOmadacId: z.string().min(1).optional(),
42
- omadaSiteId: z.string().min(1).optional(),
43
- omadaStrictSsl: createBooleanStringSchema(true),
44
- omadaTimeout: numericStringSchema,
45
-
46
- // Pre-registered REST actions for the invokeAction tool (optional; raw JSON,
47
- // parsed + validated by parseRestActions after the env parse)
48
- restActions: z.string().optional(),
49
-
50
- // Chat-face tool slice for the OpenAPI/REST surface (optional; raw
51
- // `category:rw` list, parsed + validated by parseChatTools after the env parse)
52
- chatTools: z.string().optional(),
53
-
54
- // AI Provider Configuration (use 'none' to disable AI features)
55
- aiProvider: z.enum(['ollama', 'openai', 'none']).optional(),
56
- aiUrl: z.string().url().optional(),
57
- aiModel: z.string().optional(),
58
- aiTimeout: numericStringSchema,
59
- aiApiKey: z.string().optional(),
60
-
61
- // MCP Generic Server Configuration
62
- logLevel: z.enum(['debug', 'info', 'warn', 'error']).optional().default('info'),
63
- logFormat: z.enum(['plain', 'json', 'gcp-json']).optional().default('plain'),
64
- useHttp: createBooleanStringSchema(false),
65
- stateful: createBooleanStringSchema(false),
66
- // Tool registration strategy. `eager` (default) registers every typed tool;
67
- // `graph` registers the Omada resource-graph reads (omada_browse/omada_read)
68
- // plus typed writes, shrinking the tool-schema budget for low-context models.
69
- toolRegistrationMode: z.enum(['eager', 'graph']).optional().default('eager'),
70
-
71
- // MCP Server HTTP Configuration
72
- httpPort: numericStringSchema,
73
- httpBindAddr: z.string().optional(),
74
- httpPath: z.string().optional(),
75
- httpEnableHealthcheck: createBooleanStringSchema(true),
76
- httpHealthcheckPath: z.string().optional(),
77
- httpAllowCors: createBooleanStringSchema(true),
78
- httpAllowedOrigins: z.string().optional().transform((v) => v?.split(',').map((s) => s.trim()).filter(Boolean)),
79
- httpAllowedHosts: z.string().optional().transform((v) => v?.split(',').map((s) => s.trim()).filter(Boolean)),
80
-
81
- // SSE Event Subscription Configuration
82
- sseEventsEnabled: createBooleanStringSchema(true),
83
- ownerQuestionsEnabled: createBooleanStringSchema(true),
84
- ownerQuestionsHaUsers: z.string().optional(),
85
- sseEventsPath: z.string().optional(),
86
-
87
- // Rate Limiting Configuration
88
- rateLimitEnabled: createBooleanStringSchema(true),
89
- rateLimitWindowMs: numericStringSchema,
90
- rateLimitMaxRequests: numericStringSchema,
91
- // M4: immediate-peer IPs whose forwarding headers may be trusted (CSV).
92
- rateLimitTrustedProxies: z
93
- .string()
94
- .optional()
95
- .transform((v) => v?.split(',').map((s) => s.trim()).filter(Boolean)),
96
-
97
- // Authentication Configuration
98
- authMethod: z.enum(['none', 'bearer']).optional().default('none'),
99
- authSecret: z.string().optional(),
100
- authRequireExp: createBooleanStringSchema(false),
101
- authIssuer: z.string().min(1).optional(),
102
- authAudience: z.string().min(1).optional(),
103
- permissionsConfig: z.string().optional(),
104
- })
105
- .refine(
106
- (data) => {
107
- if (data.httpBindAddr && !isValidBindAddress(data.httpBindAddr)) {
108
- return false;
109
- }
110
- return true;
111
- },
112
- {
113
- message: 'MCP_HTTP_BIND_ADDR must be a valid IPv4 or IPv6 address',
114
- path: ['httpBindAddr'],
115
- }
116
- )
117
- // zod 4 removed refine()'s message-as-function form; superRefine reports the same
118
- // message and path, naming the first invalid origin.
119
- .superRefine((data, ctx) => {
120
- const invalidOrigin = data.httpAllowedOrigins?.find((origin) => !isValidOrigin(origin));
121
- if (invalidOrigin !== undefined) {
122
- ctx.addIssue({
123
- code: 'custom',
124
- message: `MCP_HTTP_ALLOWED_ORIGINS contains invalid origin: ${invalidOrigin}`,
125
- path: ['httpAllowedOrigins'],
126
- });
127
- }
128
- })
129
- .refine(
130
- (data) => {
131
- if (data.authMethod === 'bearer' && !data.authSecret) {
132
- return false;
133
- }
134
- return true;
135
- },
136
- {
137
- message: 'MCP_AUTH_SECRET is required when MCP_AUTH_METHOD is "bearer"',
138
- path: ['authSecret'],
139
- }
140
- )
141
- .refine(
142
- (data) => {
143
- // M8: a weak signing secret undermines the whole bearer scheme.
144
- if (data.authMethod === 'bearer' && data.authSecret && data.authSecret.length < 32) {
145
- return false;
146
- }
147
- return true;
148
- },
149
- {
150
- message: 'MCP_AUTH_SECRET must be at least 32 characters when MCP_AUTH_METHOD is "bearer"',
151
- path: ['authSecret'],
152
- }
153
- )
154
- .refine(
155
- (data) => {
156
- // H5: an unauthenticated server must never be reachable off-host. Allow
157
- // method=none only when the (explicit) bind address is loopback. A missing
158
- // bind address defaults to 127.0.0.1 later, so it is safe.
159
- if (
160
- data.authMethod === 'none' &&
161
- data.httpBindAddr &&
162
- !isLoopbackAddress(data.httpBindAddr)
163
- ) {
164
- return false;
165
- }
166
- return true;
167
- },
168
- {
169
- message:
170
- 'MCP_AUTH_METHOD=none is only permitted on a loopback bind address ' +
171
- '(127.0.0.0/8 or ::1). Set MCP_AUTH_METHOD=bearer (with MCP_AUTH_SECRET) ' +
172
- 'to bind a public interface such as 0.0.0.0.',
173
- path: ['authMethod'],
174
- }
175
- );
176
-
177
- export interface EnvironmentConfig {
178
- // Plugin Enable Flags
179
- haPluginEnabled: boolean;
180
- aiPluginEnabled: boolean;
181
- omadaPluginEnabled: boolean;
182
-
183
- // Home Assistant Client Configuration (optional if using Omada only)
184
- baseUrl?: string;
185
- token?: string;
186
- strictSsl: boolean;
187
- timeout: number;
188
-
189
- // Omada Client Configuration (optional)
190
- omadaBaseUrl?: string;
191
- omadaClientId?: string;
192
- omadaClientSecret?: string;
193
- omadacId?: string;
194
- siteId?: string;
195
- omadaStrictSsl: boolean;
196
- requestTimeout?: number;
197
-
198
- // AI Provider Configuration
199
- aiProvider: AIProviderType;
200
- aiUrl: string;
201
- aiModel: string;
202
- aiTimeout: number;
203
- aiApiKey?: string;
204
-
205
- // Pre-registered REST actions for the invokeAction tool (empty = tool not registered)
206
- restActions: Record<string, RestAction>;
207
-
208
- // Chat-face (OpenAPI/REST) tool slice (undefined = legacy 8-tool default)
209
- chatTools?: ChatToolsSlice;
210
-
211
- // MCP Generic Server Configuration
212
- logLevel: 'debug' | 'info' | 'warn' | 'error';
213
- logFormat: 'plain' | 'json' | 'gcp-json';
214
- useHttp: boolean;
215
- stateful: boolean;
216
- toolRegistrationMode: 'eager' | 'graph';
217
-
218
- // MCP Server HTTP Configuration
219
- httpPort?: number;
220
- httpBindAddr?: string;
221
- httpPath?: string;
222
- httpEnableHealthcheck: boolean;
223
- httpHealthcheckPath?: string;
224
- httpAllowCors: boolean;
225
- httpAllowedOrigins?: string[];
226
- /**
227
- * Opt-in list of Host-header values (host[:port]) the server is reached by.
228
- * When set, DNS-rebinding Host validation is enforced at the transport (M3);
229
- * when unset, Host validation is OFF (origin/CORS is handled separately).
230
- */
231
- httpAllowedHosts?: string[];
232
-
233
- // SSE Event Subscription Configuration
234
- sseEventsEnabled: boolean;
235
- /** askOwner / getOwnerAnswer / cancelOwnerQuestion (HTTP mode with Home Assistant). */
236
- ownerQuestionsEnabled: boolean;
237
- /** HA user ids allowed to answer owner questions; empty = any HA user. */
238
- ownerQuestionsHaUsers: string[];
239
- sseEventsPath: string;
240
-
241
- // Rate Limiting Configuration
242
- rateLimitEnabled: boolean;
243
- rateLimitWindowMs: number;
244
- rateLimitMaxRequests: number;
245
- rateLimitTrustedProxies?: string[];
246
-
247
- // Authentication Configuration
248
- authMethod: 'none' | 'bearer';
249
- authSecret?: string;
250
- authRequireExp: boolean;
251
- authIssuer?: string;
252
- authAudience?: string;
253
- permissions: PermissionsConfig;
254
- }
255
-
256
- export function loadConfig(env: NodeJS.ProcessEnv = process.env): EnvironmentConfig {
257
- const parsed = envSchema.safeParse({
258
- // Plugin Enable Flags
259
- haPluginEnabled: env.HA_PLUGIN_ENABLED,
260
- aiPluginEnabled: env.AI_PLUGIN_ENABLED,
261
- omadaPluginEnabled: env.OMADA_PLUGIN_ENABLED,
262
-
263
- // Home Assistant Client Configuration
264
- haUrl: env.HA_URL,
265
- haToken: env.HA_TOKEN,
266
- haStrictSsl: env.HA_STRICT_SSL,
267
- haTimeout: env.HA_TIMEOUT,
268
-
269
- // Omada Client Configuration
270
- omadaBaseUrl: env.OMADA_BASE_URL,
271
- omadaClientId: env.OMADA_CLIENT_ID,
272
- omadaClientSecret: env.OMADA_CLIENT_SECRET,
273
- omadaOmadacId: env.OMADA_OMADAC_ID,
274
- omadaSiteId: env.OMADA_SITE_ID,
275
- omadaStrictSsl: env.OMADA_STRICT_SSL,
276
- omadaTimeout: env.OMADA_TIMEOUT,
277
-
278
- // Pre-registered REST actions
279
- restActions: env.MCP_REST_ACTIONS,
280
-
281
- // Chat-face tool slice
282
- chatTools: env.MCP_CHAT_TOOLS,
283
-
284
- // AI Provider Configuration
285
- aiProvider: env.AI_PROVIDER,
286
- aiUrl: env.AI_URL,
287
- aiModel: env.AI_MODEL,
288
- aiTimeout: env.AI_TIMEOUT,
289
- aiApiKey: env.AI_API_KEY,
290
-
291
- // MCP Generic Server Configuration
292
- logLevel: env.MCP_SERVER_LOG_LEVEL,
293
- logFormat: env.MCP_SERVER_LOG_FORMAT,
294
- useHttp: env.MCP_SERVER_USE_HTTP,
295
- stateful: env.MCP_SERVER_STATEFUL,
296
- toolRegistrationMode: env.MCP_TOOL_REGISTRATION_MODE,
297
-
298
- // MCP Server HTTP Configuration
299
- httpPort: env.MCP_HTTP_PORT,
300
- httpBindAddr: env.MCP_HTTP_BIND_ADDR,
301
- httpPath: env.MCP_HTTP_PATH,
302
- httpEnableHealthcheck: env.MCP_HTTP_ENABLE_HEALTHCHECK,
303
- httpHealthcheckPath: env.MCP_HTTP_HEALTHCHECK_PATH,
304
- httpAllowCors: env.MCP_HTTP_ALLOW_CORS,
305
- httpAllowedOrigins: env.MCP_HTTP_ALLOWED_ORIGINS,
306
- httpAllowedHosts: env.MCP_HTTP_ALLOWED_HOSTS,
307
-
308
- // SSE Event Subscription Configuration
309
- sseEventsEnabled: env.MCP_SSE_EVENTS_ENABLED,
310
- ownerQuestionsEnabled: env.MCP_OWNER_QUESTIONS_ENABLED,
311
- ownerQuestionsHaUsers: env.MCP_OWNER_QUESTIONS_HA_USERS,
312
- sseEventsPath: env.MCP_SSE_EVENTS_PATH,
313
-
314
- // Rate Limiting Configuration
315
- rateLimitEnabled: env.MCP_RATE_LIMIT_ENABLED,
316
- rateLimitWindowMs: env.MCP_RATE_LIMIT_WINDOW_MS,
317
- rateLimitMaxRequests: env.MCP_RATE_LIMIT_MAX_REQUESTS,
318
- rateLimitTrustedProxies: env.MCP_RATE_LIMIT_TRUSTED_PROXIES,
319
-
320
- // Authentication Configuration
321
- authMethod: env.MCP_AUTH_METHOD,
322
- authSecret: env.MCP_AUTH_SECRET,
323
- authRequireExp: env.MCP_AUTH_REQUIRE_EXP,
324
- authIssuer: env.MCP_AUTH_ISSUER,
325
- authAudience: env.MCP_AUTH_AUDIENCE,
326
- permissionsConfig: env.MCP_PERMISSIONS_CONFIG,
327
- });
328
-
329
- if (!parsed.success) {
330
- const messages = parsed.error.issues.map((issue: z.ZodIssue) => issue.message);
331
- throw new Error(`Invalid environment configuration:\n${messages.join('\n')}`);
332
- }
333
-
334
- const httpPath = parsed.data.httpPath ?? '/mcp';
335
- const httpBindAddr = parsed.data.httpBindAddr ?? '127.0.0.1';
336
- const httpPort = parsed.data.httpPort ?? 3000;
337
-
338
- // M3: default origins must be full scheme://host:port so they actually match a
339
- // real browser `Origin` header (the SDK compares it by exact string). Bare
340
- // hostnames like "localhost" never match "http://localhost:3000" and so left
341
- // DNS-rebinding/CORS protection effectively disabled for the default config.
342
- let httpAllowedOrigins =
343
- parsed.data.httpAllowedOrigins ??
344
- [`http://localhost:${httpPort}`, `http://127.0.0.1:${httpPort}`];
345
-
346
- if (httpAllowedOrigins.includes('*')) {
347
- logger.warn('Wildcard (*) origin allowed - origin validation disabled');
348
- httpAllowedOrigins = [];
349
- }
350
-
351
- // H5: make the no-auth posture loud even when the bind is loopback.
352
- if (parsed.data.authMethod === 'none') {
353
- logger.warn(
354
- 'MCP_AUTH_METHOD=none: authentication is DISABLED. This is only permitted ' +
355
- 'on a loopback bind; set MCP_AUTH_METHOD=bearer to expose the server.'
356
- );
357
- }
358
-
359
- return {
360
- // Plugin Enable Flags
361
- haPluginEnabled: parsed.data.haPluginEnabled,
362
- aiPluginEnabled: parsed.data.aiPluginEnabled,
363
- omadaPluginEnabled: parsed.data.omadaPluginEnabled,
364
-
365
- // Home Assistant Client Configuration (optional)
366
- baseUrl: parsed.data.haUrl?.replace(/\/$/, ''),
367
- token: parsed.data.haToken,
368
- strictSsl: parsed.data.haStrictSsl,
369
- timeout: parsed.data.haTimeout ?? 30000,
370
-
371
- // Omada Client Configuration (optional)
372
- omadaBaseUrl: parsed.data.omadaBaseUrl?.replace(/\/$/, ''),
373
- omadaClientId: parsed.data.omadaClientId,
374
- omadaClientSecret: parsed.data.omadaClientSecret,
375
- omadacId: parsed.data.omadaOmadacId,
376
- siteId: parsed.data.omadaSiteId,
377
- omadaStrictSsl: parsed.data.omadaStrictSsl,
378
- requestTimeout: parsed.data.omadaTimeout,
379
-
380
- // Pre-registered REST actions (throws on malformed JSON — config typos fail loudly)
381
- restActions: parseRestActions(parsed.data.restActions),
382
-
383
- // Chat-face tool slice (throws on unknown category/access — typos fail loudly)
384
- chatTools: parseChatTools(parsed.data.chatTools),
385
-
386
- // AI Provider Configuration
387
- aiProvider: (parsed.data.aiProvider ?? 'ollama') as AIProviderType,
388
- aiUrl: parsed.data.aiUrl ?? 'http://localhost:11434',
389
- aiModel: parsed.data.aiModel ?? 'qwen3:14b',
390
- aiTimeout: parsed.data.aiTimeout ?? 60000,
391
- aiApiKey: parsed.data.aiApiKey,
392
-
393
- // MCP Generic Server Configuration
394
- logLevel: parsed.data.logLevel,
395
- logFormat: parsed.data.logFormat,
396
- useHttp: parsed.data.useHttp,
397
- stateful: parsed.data.stateful,
398
- toolRegistrationMode: parsed.data.toolRegistrationMode,
399
-
400
- // MCP Server HTTP Configuration
401
- httpPort,
402
- httpBindAddr,
403
- httpPath,
404
- httpEnableHealthcheck: parsed.data.httpEnableHealthcheck,
405
- httpHealthcheckPath: parsed.data.httpHealthcheckPath,
406
- httpAllowCors: parsed.data.httpAllowCors,
407
- httpAllowedOrigins,
408
- httpAllowedHosts: parsed.data.httpAllowedHosts,
409
-
410
- // SSE Event Subscription Configuration
411
- sseEventsEnabled: parsed.data.sseEventsEnabled,
412
- ownerQuestionsEnabled: parsed.data.ownerQuestionsEnabled,
413
- ownerQuestionsHaUsers: (parsed.data.ownerQuestionsHaUsers ?? '').split(',').map((u) => u.trim()).filter(Boolean),
414
- sseEventsPath: parsed.data.sseEventsPath ?? '/subscribe_events',
415
-
416
- // Rate Limiting Configuration
417
- rateLimitEnabled: parsed.data.rateLimitEnabled,
418
- rateLimitWindowMs: parsed.data.rateLimitWindowMs ?? 60000,
419
- rateLimitMaxRequests: parsed.data.rateLimitMaxRequests ?? 100,
420
- rateLimitTrustedProxies: parsed.data.rateLimitTrustedProxies,
421
-
422
- // Authentication Configuration
423
- authMethod: parsed.data.authMethod,
424
- authSecret: parsed.data.authSecret,
425
- authRequireExp: parsed.data.authRequireExp,
426
- authIssuer: parsed.data.authIssuer,
427
- authAudience: parsed.data.authAudience,
428
- permissions: parsePermissionsConfig(parsed.data.permissionsConfig),
429
- };
430
- }
431
-
432
- export function validateConfig(config: EnvironmentConfig): void {
433
- if (!config.baseUrl || !config.token) {
434
- throw new Error('Invalid configuration: missing baseUrl or token');
435
- }
436
- }
437
-
438
- export function loadConfigFromEnv(): EnvironmentConfig {
439
- return loadConfig();
440
- }