@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,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
- }