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