@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,284 +0,0 @@
1
- import type { OmadaApiResponse, PaginatedResult } from '../types/index.js';
2
-
3
- import type { RequestHandler } from './request.js';
4
- import type { SiteOperations } from './site.js';
5
-
6
- /**
7
- * Network-related operations for the Omada API.
8
- * Covers internet, LAN, WLAN, firewall, and port forwarding configurations.
9
- */
10
- export class NetworkOperations {
11
- constructor(
12
- private readonly request: RequestHandler,
13
- private readonly site: SiteOperations,
14
- private readonly buildPath: (path: string, version?: string) => string
15
- ) {}
16
-
17
- /**
18
- * Get internet configuration info for a site.
19
- * OperationId: getInternet
20
- */
21
- public async getInternetInfo(siteId?: string): Promise<unknown> {
22
- const resolvedSiteId = this.site.resolveSiteId(siteId);
23
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/internet`);
24
- const response = await this.request.get<OmadaApiResponse<unknown>>(path);
25
- return this.request.ensureSuccess(response);
26
- }
27
-
28
- /**
29
- * Get port forwarding status for a specific type (User or UPnP).
30
- * OperationId: getPortForwardStatus
31
- *
32
- * @param type - Port forwarding type: 'User' or 'UPnP'
33
- * @param siteId - Optional site ID (uses default if not provided)
34
- * @param page - Page number (required by API, default: 1)
35
- * @param pageSize - Page size (required by API, range: 1-1000, default: 10)
36
- */
37
- public async getPortForwardingStatus(type: 'User' | 'UPnP', siteId?: string, page = 1, pageSize = 10): Promise<PaginatedResult<unknown>> {
38
- const resolvedSiteId = this.site.resolveSiteId(siteId);
39
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/insight/port-forwarding/${encodeURIComponent(type)}`);
40
-
41
- const response = await this.request.get<OmadaApiResponse<PaginatedResult<unknown>>>(path, {
42
- page,
43
- pageSize,
44
- });
45
-
46
- return this.request.ensureSuccess(response);
47
- }
48
-
49
- /**
50
- * Get LAN network list (v2 API) with pagination.
51
- * OperationId: getLanNetworkListV2
52
- */
53
- public async getLanNetworkList(siteId?: string): Promise<unknown[]> {
54
- const resolvedSiteId = this.site.resolveSiteId(siteId);
55
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/lan-networks`, 'v2');
56
- return await this.request.fetchPaginated<unknown>(path);
57
- }
58
-
59
- /**
60
- * Get LAN profile list with pagination.
61
- * OperationId: getLanProfileList
62
- */
63
- public async getLanProfileList(siteId?: string): Promise<unknown[]> {
64
- const resolvedSiteId = this.site.resolveSiteId(siteId);
65
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/lan-profiles`);
66
- return await this.request.fetchPaginated<unknown>(path);
67
- }
68
-
69
- /**
70
- * Get WLAN group list.
71
- * OperationId: getWlanGroupList
72
- */
73
- public async getWlanGroupList(siteId?: string): Promise<unknown[]> {
74
- const resolvedSiteId = this.site.resolveSiteId(siteId);
75
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/wireless-network/wlans`);
76
- const response = await this.request.get<OmadaApiResponse<unknown[]>>(path);
77
- return this.request.ensureSuccess(response);
78
- }
79
-
80
- /**
81
- * Get SSID list for a specific WLAN group.
82
- * OperationId: getSsidList
83
- *
84
- * @param wlanId - WLAN group ID (can be obtained from getWlanGroupList)
85
- */
86
- public async getSsidList(wlanId: string, siteId?: string): Promise<unknown[]> {
87
- if (!wlanId) {
88
- throw new Error('A wlanId must be provided. Use omada_read at path /wifi/groups to get available WLAN group IDs.');
89
- }
90
-
91
- const resolvedSiteId = this.site.resolveSiteId(siteId);
92
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/wireless-network/wlans/${encodeURIComponent(wlanId)}/ssids`);
93
- return await this.request.fetchPaginated<unknown>(path);
94
- }
95
-
96
- /**
97
- * Get detailed information for a specific SSID.
98
- * OperationId: getSsidDetail
99
- *
100
- * @param wlanId - WLAN group ID (can be obtained from getWlanGroupList)
101
- * @param ssidId - SSID ID (can be obtained from getSsidList)
102
- */
103
- public async getSsidDetail(wlanId: string, ssidId: string, siteId?: string): Promise<unknown> {
104
- if (!wlanId) {
105
- throw new Error('A wlanId must be provided. Use omada_read at path /wifi/groups to get available WLAN group IDs.');
106
- }
107
- if (!ssidId) {
108
- throw new Error('An ssidId must be provided. Use omada_read at path /wifi/ssids to get available SSID IDs.');
109
- }
110
-
111
- const resolvedSiteId = this.site.resolveSiteId(siteId);
112
- const path = this.buildPath(
113
- `/sites/${encodeURIComponent(resolvedSiteId)}/wireless-network/wlans/${encodeURIComponent(wlanId)}/ssids/${encodeURIComponent(ssidId)}`
114
- );
115
- const response = await this.request.get<OmadaApiResponse<unknown>>(path);
116
- return this.request.ensureSuccess(response);
117
- }
118
-
119
- /**
120
- * Get firewall settings for a site.
121
- * OperationId: getFirewallSetting
122
- */
123
- public async getFirewallSetting(siteId?: string): Promise<unknown> {
124
- const resolvedSiteId = this.site.resolveSiteId(siteId);
125
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/firewall`);
126
- const response = await this.request.get<OmadaApiResponse<unknown>>(path);
127
- return this.request.ensureSuccess(response);
128
- }
129
-
130
- /**
131
- * List every SSID on the site, flattened across WLAN groups.
132
- * OperationId: listAllSsids — returns [{wlanId, wlanName, ssidList:[{ssidId, ssidName}]}].
133
- */
134
- public async listAllSsids(siteId?: string): Promise<WlanSimpleInfo[]> {
135
- const resolvedSiteId = this.site.resolveSiteId(siteId);
136
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/wireless-network/ssids`);
137
- const response = await this.request.get<OmadaApiResponse<WlanSimpleInfo[]>>(path);
138
- return this.request.ensureSuccess(response);
139
- }
140
-
141
- /**
142
- * List time-range profiles for the site.
143
- * OperationId: listTimeRangeProfiles — returns [{profileId, name, dayMode, ...}].
144
- */
145
- public async listTimeRangeProfiles(siteId?: string): Promise<TimeRangeProfileInfo[]> {
146
- const resolvedSiteId = this.site.resolveSiteId(siteId);
147
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/time-range-profiles`);
148
- const response = await this.request.get<OmadaApiResponse<TimeRangeProfileInfo[]>>(path);
149
- return this.request.ensureSuccess(response);
150
- }
151
-
152
- /**
153
- * Create a time-range profile. The API returns no profileId — callers must
154
- * re-list and match by name to obtain the created profile's id.
155
- * OperationId: createSitesTimeRangeProfiles
156
- */
157
- public async createTimeRangeProfile(profile: CreateTimeRangeProfile, siteId?: string): Promise<void> {
158
- const resolvedSiteId = this.site.resolveSiteId(siteId);
159
- const path = this.buildPath(`/sites/${encodeURIComponent(resolvedSiteId)}/time-range-profiles`);
160
- const response = await this.request.post<OmadaApiResponse<unknown>>(path, profile);
161
- this.request.ensureSuccess(response);
162
- }
163
-
164
- /**
165
- * Update the WLAN-schedule config of a single SSID.
166
- * OperationId: updateSitesWirelessNetworkWlansSsidsUpdateWlanSchedule
167
- * Omada has no direct SSID on/off — the WLAN schedule (action 0 = radio off
168
- * during the scheduled period) is the controller's sanctioned mechanism.
169
- */
170
- public async updateSsidWlanSchedule(
171
- wlanId: string,
172
- ssidId: string,
173
- schedule: { wlanScheduleEnable: boolean; action?: number; scheduleId?: string },
174
- siteId?: string
175
- ): Promise<void> {
176
- const resolvedSiteId = this.site.resolveSiteId(siteId);
177
- const path = this.buildPath(
178
- `/sites/${encodeURIComponent(resolvedSiteId)}/wireless-network/wlans/${encodeURIComponent(wlanId)}/ssids/${encodeURIComponent(ssidId)}/update-wlan-schedule`
179
- );
180
- const response = await this.request.patch<OmadaApiResponse<unknown>>(path, schedule);
181
- this.request.ensureSuccess(response);
182
- }
183
-
184
- /**
185
- * Enable or disable an SSID (e.g. the guest network) by name or ssidId.
186
- *
187
- * Omada 6.x exposes no direct enable flag on an SSID; the sanctioned lever is
188
- * the per-SSID WLAN schedule. Disabling applies an always-on "radio off"
189
- * schedule (a 24/7 time-range profile is created once and reused); enabling
190
- * simply turns the schedule off again. Fully reversible, and unlike a MAC
191
- * filter it actually stops the SSID from broadcasting.
192
- */
193
- public async setSsidEnabled(ssid: string, enabled: boolean, siteId?: string): Promise<SsidEnableResult> {
194
- const wlans = await this.listAllSsids(siteId);
195
- const needle = ssid.trim().toLowerCase();
196
- let match: { wlanId: string; ssidId: string; ssidName: string } | undefined;
197
- for (const wlan of wlans) {
198
- for (const s of wlan.ssidList ?? []) {
199
- if (s.ssidId === ssid || s.ssidName.toLowerCase() === needle) {
200
- match = { wlanId: wlan.wlanId, ssidId: s.ssidId, ssidName: s.ssidName };
201
- break;
202
- }
203
- }
204
- if (match) break;
205
- }
206
- if (!match) {
207
- const available = wlans.flatMap((w) => (w.ssidList ?? []).map((s) => s.ssidName)).join(', ');
208
- throw new Error(`SSID '${ssid}' not found. Available SSIDs: ${available || '(none)'}`);
209
- }
210
-
211
- if (enabled) {
212
- await this.updateSsidWlanSchedule(match.wlanId, match.ssidId, { wlanScheduleEnable: false }, siteId);
213
- return { ...match, enabled: true, method: 'wlan-schedule disabled' };
214
- }
215
-
216
- const scheduleId = await this.ensureAlwaysProfile(siteId);
217
- await this.updateSsidWlanSchedule(
218
- match.wlanId,
219
- match.ssidId,
220
- { wlanScheduleEnable: true, action: 0, scheduleId },
221
- siteId
222
- );
223
- return { ...match, enabled: false, method: 'wlan-schedule radio-off 24/7', scheduleId };
224
- }
225
-
226
- /**
227
- * Find-or-create the 24/7 time-range profile used to switch SSIDs off.
228
- * Created once per site under a fixed name, then reused forever.
229
- */
230
- private async ensureAlwaysProfile(siteId?: string): Promise<string> {
231
- const existing = await this.listTimeRangeProfiles(siteId);
232
- const found = existing.find((p) => p.name === ALWAYS_PROFILE_NAME);
233
- if (found) {
234
- return found.profileId;
235
- }
236
-
237
- await this.createTimeRangeProfile(
238
- {
239
- name: ALWAYS_PROFILE_NAME,
240
- dayMode: 0, // Every Day
241
- timeList: [{ dayType: 1, startTimeH: 0, startTimeM: 0, endTimeH: 24, endTimeM: 0 }],
242
- },
243
- siteId
244
- );
245
-
246
- // The create endpoint returns no id — re-list and match by name.
247
- const after = await this.listTimeRangeProfiles(siteId);
248
- const created = after.find((p) => p.name === ALWAYS_PROFILE_NAME);
249
- if (!created) {
250
- throw new Error(`Time-range profile '${ALWAYS_PROFILE_NAME}' was created but not found on re-list`);
251
- }
252
- return created.profileId;
253
- }
254
- }
255
-
256
- /** Fixed name of the reusable 24/7 profile that backs omada_setSsidEnabled(false). */
257
- export const ALWAYS_PROFILE_NAME = 'mcp-always (24/7, used by omada_setSsidEnabled)';
258
-
259
- export interface WlanSimpleInfo {
260
- wlanId: string;
261
- wlanName: string;
262
- ssidList?: Array<{ ssidId: string; ssidName: string }>;
263
- }
264
-
265
- export interface TimeRangeProfileInfo {
266
- profileId: string;
267
- name: string;
268
- dayMode?: number;
269
- }
270
-
271
- export interface CreateTimeRangeProfile {
272
- name: string;
273
- dayMode: number;
274
- timeList: Array<{ dayType: number; startTimeH: number; startTimeM: number; endTimeH: number; endTimeM: number }>;
275
- }
276
-
277
- export interface SsidEnableResult {
278
- wlanId: string;
279
- ssidId: string;
280
- ssidName: string;
281
- enabled: boolean;
282
- method: string;
283
- scheduleId?: string;
284
- }
@@ -1,365 +0,0 @@
1
- import type { Dispatcher } from 'undici';
2
- import type { OmadaApiResponse, PaginatedResult } from '../types/index.js';
3
- import { logger } from '../utils/logger.js';
4
- import { createTlsDispatcher, tlsAwareFetch, type FetchInitWithDispatcher } from '../utils/tlsDispatcher.js';
5
-
6
- import type { AuthManager } from './auth.js';
7
-
8
- const DEFAULT_PAGE_SIZE = 200;
9
-
10
- /** An error the Omada controller reported, with its structured errorCode when it gave one. */
11
- export class OmadaApiError extends Error {
12
- constructor(
13
- message: string,
14
- readonly errorCode?: number
15
- ) {
16
- super(message);
17
- this.name = 'OmadaApiError';
18
- }
19
- }
20
-
21
- export interface RequestOptions {
22
- method?: string;
23
- url: string;
24
- params?: Record<string, unknown>;
25
- data?: unknown;
26
- headers?: Record<string, string>;
27
- }
28
-
29
- export interface RequestHandlerConfig {
30
- baseUrl: string;
31
- timeout?: number;
32
- strictSsl?: boolean;
33
- }
34
-
35
- /**
36
- * HTTP request handler for Omada API calls with authentication and retry logic.
37
- */
38
- export class RequestHandler {
39
- private readonly baseUrl: string;
40
- private readonly timeout: number;
41
- /** Per-client undici dispatcher; set only when strictSsl is disabled (H2). */
42
- private readonly dispatcher?: Dispatcher;
43
-
44
- constructor(
45
- config: RequestHandlerConfig,
46
- private readonly auth: AuthManager
47
- ) {
48
- this.baseUrl = config.baseUrl;
49
- this.timeout = config.timeout ?? 30000;
50
- this.dispatcher = createTlsDispatcher(config.strictSsl, config.baseUrl);
51
- }
52
-
53
- /**
54
- * Make a GET request to the Omada API.
55
- */
56
- public async get<T>(path: string, params?: Record<string, unknown>): Promise<T> {
57
- return await this.request<T>({ method: 'GET', url: path, params });
58
- }
59
-
60
- /**
61
- * Make a PATCH request to the Omada API.
62
- */
63
- public async patch<T>(path: string, data?: unknown): Promise<T> {
64
- return await this.request<T>({ method: 'PATCH', url: path, data });
65
- }
66
-
67
- /**
68
- * Make a PUT request to the Omada API.
69
- */
70
- public async put<T>(path: string, data?: unknown): Promise<T> {
71
- return await this.request<T>({ method: 'PUT', url: path, data });
72
- }
73
-
74
- /**
75
- * Make a POST request to the Omada API.
76
- */
77
- public async post<T>(path: string, data?: unknown): Promise<T> {
78
- return await this.request<T>({ method: 'POST', url: path, data });
79
- }
80
-
81
- /**
82
- * Make an arbitrary HTTP request to the Omada API.
83
- */
84
- public async request<T>(config: RequestOptions, retry = true): Promise<T> {
85
- const accessToken = await this.auth.getAccessToken();
86
-
87
- const method = (config.method ?? 'GET').toUpperCase();
88
- const url = new URL(config.url, this.baseUrl);
89
-
90
- // Add query parameters
91
- if (config.params) {
92
- for (const [key, value] of Object.entries(config.params)) {
93
- if (value !== undefined && value !== null) {
94
- url.searchParams.set(key, String(value));
95
- }
96
- }
97
- }
98
-
99
- const headers: Record<string, string> = {
100
- 'Content-Type': 'application/json',
101
- Accept: 'application/json',
102
- Authorization: `AccessToken=${accessToken}`,
103
- ...(config.headers ?? {}),
104
- };
105
-
106
- logger.info('Omada request', {
107
- method,
108
- url: config.url,
109
- params: config.params,
110
- siteId: config.params?.siteId ?? undefined,
111
- });
112
-
113
- logger.debug('Omada request details', {
114
- method,
115
- url: config.url,
116
- headers: this.sanitizeHeaders(headers),
117
- params: config.params ?? null,
118
- data: this.sanitizePayload(config.data),
119
- });
120
-
121
- try {
122
- // For self-signed certificates we relax TLS validation for THIS client
123
- // only, via a per-instance undici dispatcher (H2). Node's global fetch
124
- // ignores the legacy `agent` option, and toggling the process-global
125
- // NODE_TLS_REJECT_UNAUTHORIZED env var would disable validation for every
126
- // concurrent outbound request in the process.
127
- const init: FetchInitWithDispatcher = {
128
- method,
129
- headers,
130
- body: config.data ? JSON.stringify(config.data) : undefined,
131
- signal: AbortSignal.timeout(this.timeout),
132
- };
133
- if (this.dispatcher) {
134
- init.dispatcher = this.dispatcher;
135
- }
136
-
137
- // tlsAwareFetch: dispatcher requests use undici's own fetch (pairing rule).
138
- const response = await tlsAwareFetch(url.toString(), init);
139
-
140
- logger.info('Omada response', {
141
- method,
142
- url: config.url,
143
- status: response.status,
144
- });
145
-
146
- // Parse response body
147
- const responseData = (await response.json()) as T;
148
-
149
- logger.debug('Omada response payload', {
150
- method,
151
- url: config.url,
152
- status: response.status,
153
- data: this.sanitizePayload(responseData),
154
- });
155
-
156
- // Check if the response data indicates an authentication error
157
- const errorCode = (responseData as { errorCode?: number } | undefined)?.errorCode;
158
- const errorMsg = (responseData as { msg?: string } | undefined)?.msg;
159
-
160
- if (retry && (this.isAuthErrorCode(errorCode) || this.isTokenExpiredMessage(errorMsg))) {
161
- logger.warn('Omada authentication error in response, retrying with fresh token', {
162
- method,
163
- url: config.url,
164
- errorCode,
165
- message: errorMsg,
166
- });
167
- this.auth.clearToken();
168
- return this.request<T>(config, false);
169
- }
170
-
171
- // Handle HTTP errors after checking for auth errors in body
172
- if (!response.ok && !this.isAuthErrorCode(errorCode)) {
173
- logger.error('Omada request failed', {
174
- method,
175
- url: config.url,
176
- status: response.status,
177
- statusText: response.statusText,
178
- errorCode,
179
- message: errorMsg,
180
- });
181
- // Surface the controller's own structured errorCode/msg when present
182
- // (safe diagnostic data — the controller's description, not an internal
183
- // hostname/header) so a rejected request explains itself instead of
184
- // collapsing to a bare HTTP status like "400 ".
185
- const parts = [`${response.status} ${response.statusText}`.trim()];
186
- if (errorMsg) {
187
- parts.push(`- ${errorMsg}`);
188
- }
189
- if (errorCode !== undefined && errorCode !== 0) {
190
- parts.push(`(errorCode ${errorCode})`);
191
- }
192
- throw new OmadaApiError(`Omada API request failed: ${parts.join(' ')}`, errorCode);
193
- }
194
-
195
- return responseData;
196
- } catch (error) {
197
- logger.error('Omada request failed', {
198
- method,
199
- url: config.url,
200
- message: error instanceof Error ? error.message : String(error),
201
- });
202
-
203
- if (!retry) {
204
- throw error;
205
- }
206
-
207
- // Check if it's a network/timeout error that might be auth-related
208
- const isNetworkError = error instanceof TypeError ||
209
- (error instanceof Error && error.name === 'AbortError');
210
-
211
- if (isNetworkError) {
212
- throw error;
213
- }
214
-
215
- throw error;
216
- }
217
- }
218
-
219
- /**
220
- * Fetch all pages of a paginated API endpoint.
221
- */
222
- public async fetchPaginated<T>(path: string, params: Record<string, unknown> = {}): Promise<T[]> {
223
- const records: T[] = [];
224
- let page = 1;
225
- let totalRows: number | undefined;
226
-
227
- // Fetch sequential pages because OpenAPI requires explicit pagination parameters.
228
- do {
229
- const response = await this.get<OmadaApiResponse<PaginatedResult<T>>>(path, {
230
- ...params,
231
- page,
232
- pageSize: DEFAULT_PAGE_SIZE,
233
- });
234
-
235
- const result = this.ensureSuccess(response);
236
- const pageData = result.data ?? [];
237
- totalRows = result.totalRows ?? totalRows;
238
-
239
- records.push(...pageData);
240
- page += 1;
241
-
242
- if (pageData.length === 0) {
243
- break;
244
- }
245
- } while (!totalRows || records.length < totalRows);
246
-
247
- return records;
248
- }
249
-
250
- /**
251
- * Ensure an Omada API response indicates success.
252
- * @throws {Error} If the response contains an error code
253
- */
254
- public ensureSuccess<T>(response: OmadaApiResponse<T>): T {
255
- if (response.errorCode !== 0) {
256
- logger.error('Omada API error', {
257
- errorCode: response.errorCode,
258
- message: response.msg,
259
- });
260
- throw new OmadaApiError(response.msg ?? 'Omada API request failed', response.errorCode);
261
- }
262
-
263
- return (response.result ?? ({} as T)) as T;
264
- }
265
-
266
- /**
267
- * Check if an error code indicates an authentication error.
268
- */
269
- private isAuthErrorCode(errorCode?: number): boolean {
270
- if (errorCode === undefined) {
271
- return false;
272
- }
273
-
274
- return [-44106, -44111, -44112, -44113, -44114, -44116].includes(errorCode);
275
- }
276
-
277
- /**
278
- * Check if an error message indicates token expiration.
279
- */
280
- private isTokenExpiredMessage(message?: string): boolean {
281
- if (!message) {
282
- return false;
283
- }
284
-
285
- const lowerMsg = message.toLowerCase();
286
- return (
287
- lowerMsg.includes('access token has expired') ||
288
- lowerMsg.includes('token has expired') ||
289
- lowerMsg.includes('token expired') ||
290
- lowerMsg.includes('re-initiate the refreshtoken')
291
- );
292
- }
293
-
294
- /**
295
- * Sanitize HTTP headers for logging, masking sensitive values.
296
- */
297
- private sanitizeHeaders(headers: Record<string, string> | undefined): Record<string, unknown> | undefined {
298
- if (!headers) {
299
- return undefined;
300
- }
301
-
302
- const sanitized: Record<string, unknown> = {};
303
- for (const [key, value] of Object.entries(headers)) {
304
- sanitized[key] = this.isSensitiveKey(key) ? this.maskValue(value) : value;
305
- }
306
-
307
- return sanitized;
308
- }
309
-
310
- /**
311
- * Sanitize a payload for logging, masking sensitive values.
312
- */
313
- private sanitizePayload(payload: unknown): unknown {
314
- if (!payload || typeof payload !== 'object') {
315
- return payload;
316
- }
317
-
318
- if (Array.isArray(payload)) {
319
- return payload.map((item) => this.sanitizePayload(item));
320
- }
321
-
322
- const sanitized: Record<string, unknown> = {};
323
- for (const [key, value] of Object.entries(payload)) {
324
- sanitized[key] = this.isSensitiveKey(key) ? this.maskValue(value) : this.sanitizePayload(value);
325
- }
326
-
327
- return sanitized;
328
- }
329
-
330
- /**
331
- * Check if a key name indicates sensitive data.
332
- */
333
- private isSensitiveKey(key: string): boolean {
334
- const normalized = key.toLowerCase();
335
- return (
336
- normalized.includes('authorization') ||
337
- normalized.includes('token') ||
338
- normalized.includes('secret') ||
339
- normalized.includes('password') ||
340
- normalized.includes('client_id')
341
- );
342
- }
343
-
344
- /**
345
- * Mask a sensitive value for logging.
346
- */
347
- private maskValue(value: unknown): unknown {
348
- if (typeof value === 'string') {
349
- if (value.length <= 8) {
350
- return '********';
351
- }
352
- return `${value.slice(0, 4)}…${value.slice(-4)}`;
353
- }
354
-
355
- if (Array.isArray(value)) {
356
- return value.map(() => '********');
357
- }
358
-
359
- if (typeof value === 'object' && value !== null) {
360
- return '[masked-object]';
361
- }
362
-
363
- return '********';
364
- }
365
- }
@@ -1,51 +0,0 @@
1
- import type { OmadaApiResponse, PaginatedResult, GetThreatListOptions, ThreatInfo } from '../types/index.js';
2
- import type { RequestHandler } from './request.js';
3
-
4
- /**
5
- * Security-related operations for the Omada API.
6
- * Handles threat management and security features.
7
- */
8
- export class SecurityOperations {
9
- constructor(
10
- private readonly request: RequestHandler,
11
- private readonly buildPath: (path: string) => string
12
- ) {}
13
-
14
- /**
15
- * Get the global view threat management list.
16
- * operationId: getGlobalThreatList
17
- *
18
- * @param options - Threat list query options
19
- * @returns Paginated list of threat information
20
- */
21
- async getThreatList(options: GetThreatListOptions): Promise<PaginatedResult<ThreatInfo>> {
22
- const params: Record<string, unknown> = {
23
- archived: options.archived,
24
- page: options.page,
25
- pageSize: options.pageSize,
26
- 'filters.startTime': options.startTime,
27
- 'filters.endTime': options.endTime,
28
- };
29
-
30
- if (options.siteList) {
31
- params.siteList = options.siteList;
32
- }
33
-
34
- if (options.severity !== undefined) {
35
- params['filters.severity'] = options.severity;
36
- }
37
-
38
- if (options.sortTime) {
39
- params['sorts.time'] = options.sortTime;
40
- }
41
-
42
- if (options.searchKey) {
43
- params.searchKey = options.searchKey;
44
- }
45
-
46
- const path = this.buildPath('/security/threat-management');
47
-
48
- const response = await this.request.get<OmadaApiResponse<PaginatedResult<ThreatInfo>>>(path, params);
49
- return this.request.ensureSuccess(response);
50
- }
51
- }