@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,52 +0,0 @@
1
- /**
2
- * System tools - Restart, logs, updates
3
- */
4
-
5
- import type { McpServer } from '@modelcontextprotocol/server';
6
- import type { HaClient } from '../../haClient/index.js';
7
- import { emptySchema, systemLogSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
8
- import type { z } from 'zod';
9
-
10
- type SystemLogArgs = z.infer<typeof systemLogSchema>;
11
-
12
- export function registerRestartHomeAssistantTool(server: McpServer, client: HaClient): void {
13
- server.registerTool(
14
- 'restartHomeAssistant',
15
- {
16
- description: 'Restart the Home Assistant server. Use with caution.',
17
- inputSchema: emptySchema,
18
- },
19
- wrapToolHandler('restartHomeAssistant', async () => {
20
- await client.restartServer();
21
- return toToolResult({ success: true, message: 'Home Assistant restart initiated' });
22
- }, Permission.ADMIN)
23
- );
24
- }
25
-
26
- export function registerGetSystemLogTool(server: McpServer, client: HaClient): void {
27
- server.registerTool(
28
- 'getSystemLog',
29
- {
30
- description: 'Get system log entries from the Home Assistant logbook. Shows recent events and state changes. Default: last 24h, 100 entries.',
31
- inputSchema: systemLogSchema,
32
- },
33
- wrapToolHandler('getSystemLog', async ({ hours = 24, entity_id, limit = 100 }: SystemLogArgs) => {
34
- const log = await client.getSystemLog({ hours, entity_id, limit });
35
- return toToolResult({ hours, limit, entity_id: entity_id || null, entries: log });
36
- }, Permission.ADMIN)
37
- );
38
- }
39
-
40
- export function registerCheckUpdatesTool(server: McpServer, client: HaClient): void {
41
- server.registerTool(
42
- 'checkUpdates',
43
- {
44
- description: 'Check for available updates for Home Assistant Core, Supervisor, and OS. Requires Home Assistant OS or Supervised installation.',
45
- inputSchema: emptySchema,
46
- },
47
- wrapToolHandler('checkUpdates', async () => {
48
- const updates = await client.getAvailableUpdates();
49
- return toToolResult(updates);
50
- }, Permission.ADMIN)
51
- );
52
- }
@@ -1,24 +0,0 @@
1
- /**
2
- * triggerAutomation tool - Manually trigger an automation
3
- */
4
-
5
- import type { McpServer } from '@modelcontextprotocol/server';
6
- import type { HaClient } from '../../haClient/index.js';
7
- import { triggerAutomationSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
8
- import type { z } from 'zod';
9
-
10
- type TriggerAutomationArgs = z.infer<typeof triggerAutomationSchema>;
11
-
12
- export function registerTriggerAutomationTool(server: McpServer, client: HaClient): void {
13
- server.registerTool(
14
- 'triggerAutomation',
15
- {
16
- description: 'Manually trigger a Home Assistant automation. Can optionally pass variables to the automation.',
17
- inputSchema: triggerAutomationSchema,
18
- },
19
- wrapToolHandler('triggerAutomation', async ({ entity_id, variables }: TriggerAutomationArgs) => {
20
- const result = await client.triggerAutomation(entity_id, variables as Record<string, unknown> | undefined);
21
- return toToolResult({ success: true, entity_id, message: 'Automation triggered successfully', context: result.context });
22
- }, Permission.CONFIGURE)
23
- );
24
- }
@@ -1,78 +0,0 @@
1
- /**
2
- * Tools index - registers all MCP tools organized by domain
3
- */
4
-
5
- import type { McpServer } from '@modelcontextprotocol/server';
6
- import type { HaClient } from '../haClient/index.js';
7
- import type { LocalAIClient } from '../localAI/index.js';
8
- import type { OmadaClient } from '../omadaClient/index.js';
9
- import { logger } from '../utils/logger.js';
10
-
11
- // Tool registration functions by domain
12
- import { registerHomeAssistantTools } from './homeassistant/index.js';
13
- import { registerOmadaTools, type OmadaRegistrationMode } from './omada/index.js';
14
- import { registerAITools } from './ai/index.js';
15
- import { registerInfraTools, type RestAction } from './infra/index.js';
16
- import { registerOwnerQuestionTools } from './homeassistant/askOwner.js';
17
- import type { OwnerQuestionService } from '../ownerQuestions/service.js';
18
-
19
- export interface RegisterToolsOptions {
20
- server: McpServer;
21
- haClient?: HaClient;
22
- omadaClient?: OmadaClient;
23
- aiClient?: LocalAIClient;
24
- /** Pre-registered REST actions for invokeAction (empty/undefined = tool not registered). */
25
- restActions?: Record<string, RestAction>;
26
- /** Tool registration strategy for the Omada plugin (default 'eager'). */
27
- toolRegistrationMode?: OmadaRegistrationMode;
28
- /** Owner questions (askOwner & co.); registered only when the service is running. */
29
- ownerQuestions?: OwnerQuestionService;
30
- }
31
-
32
- /**
33
- * Register all available tools based on configured clients
34
- */
35
- export function registerAllTools(options: RegisterToolsOptions): void {
36
- const { server, haClient, omadaClient, aiClient, restActions, toolRegistrationMode, ownerQuestions } = options;
37
- logger.debug('Registering all tools');
38
-
39
- let totalTools = 0;
40
-
41
- // Home Assistant tools (if client provided)
42
- if (haClient) {
43
- const haToolCount = registerHomeAssistantTools(server, haClient);
44
- totalTools += haToolCount;
45
- if (ownerQuestions) {
46
- totalTools += registerOwnerQuestionTools(server, ownerQuestions);
47
- }
48
- }
49
-
50
- // Omada tools (if client provided)
51
- if (omadaClient) {
52
- const omadaToolCount = registerOmadaTools(server, omadaClient, toolRegistrationMode);
53
- totalTools += omadaToolCount;
54
- }
55
-
56
- // AI tools (always register, but may be disabled if no aiClient)
57
- const aiToolCount = registerAITools(server, aiClient);
58
- totalTools += aiToolCount;
59
-
60
- // Infra tools (only when REST actions are configured)
61
- totalTools += registerInfraTools(server, restActions ?? {});
62
-
63
- logger.info('All tools registered successfully', {
64
- totalTools,
65
- haEnabled: !!haClient,
66
- omadaEnabled: !!omadaClient,
67
- aiEnabled: !!aiClient,
68
- infraEnabled: Object.keys(restActions ?? {}).length > 0,
69
- });
70
- }
71
-
72
- // Re-export common utilities
73
- export { toToolResult, wrapToolHandler, Permission } from './common.js';
74
-
75
- // Re-export domain-specific registration functions for selective use
76
- export { registerHomeAssistantTools } from './homeassistant/index.js';
77
- export { registerOmadaTools } from './omada/index.js';
78
- export { registerAITools } from './ai/index.js';
@@ -1,95 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- /**
4
- * A pre-registered REST action the `invokeAction` tool may fire.
5
- *
6
- * SECURITY MODEL: the LLM caller supplies ONE thing — an action NAME — which is
7
- * exact-match looked up here. Method, URL, auth, and timeout all come from this
8
- * operator-controlled config; no model-supplied string is ever interpolated into
9
- * any part of the request. A malformed/hostile name can only fail the lookup.
10
- */
11
- export interface RestAction {
12
- /** Human hint surfaced in the tool description so the agent picks well. */
13
- description?: string;
14
- method: 'GET' | 'POST';
15
- url: string;
16
- /** Optional Authorization: Bearer <token> (e.g. watchtower's HTTP-API token). */
17
- bearerToken?: string;
18
- /** Per-action timeout; some actions (watchtower update = image pulls) run long. */
19
- timeoutMs?: number;
20
- /**
21
- * Minimum interval between firings of THIS action (rate limit). Defaults to
22
- * 60s when unset; 0 disables. Caps the blast radius of a prompt-injected
23
- * model looping a legitimate action (deploy storms / endpoint DoS) — the
24
- * name-only lookup already stops forged actions; this stops repeated real ones.
25
- */
26
- cooldownMs?: number;
27
- }
28
-
29
- /**
30
- * Action names are deliberately boring: lowercase alnum with -/_ separators,
31
- * 1–64 chars, must start alphanumeric. This rejects prototype-pollution shaped
32
- * keys (`__proto__`), path/URL fragments, and whitespace tricks at CONFIG time,
33
- * so the runtime lookup only ever sees tame identifiers.
34
- */
35
- const ACTION_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
36
-
37
- const actionSchema = z.object({
38
- description: z.string().max(200).optional(),
39
- method: z.enum(['GET', 'POST']).default('POST'),
40
- url: z.string().url({ message: 'each action needs a valid http(s) url' }),
41
- bearerToken: z.string().min(1).optional(),
42
- timeoutMs: z.number().int().min(1000).max(300_000).optional(),
43
- // 0 = explicitly disabled; capped at 24h so a config typo can't brick an action.
44
- cooldownMs: z.number().int().min(0).max(86_400_000).optional(),
45
- });
46
-
47
- /**
48
- * Parse MCP_REST_ACTIONS (JSON):
49
- * {"update-node-u2":{"method":"POST","url":"http://192.168.0.9:8425/v1/update","bearerToken":"…","description":"deploy latest images on node-u2"}}
50
- *
51
- * Returns an empty map when unset; throws on malformed input so a config typo
52
- * fails loudly at startup instead of silently disabling the tool.
53
- */
54
- export function parseRestActions(raw: string | undefined): Record<string, RestAction> {
55
- if (!raw || raw.trim() === '') {
56
- return {};
57
- }
58
-
59
- let json: unknown;
60
- try {
61
- json = JSON.parse(raw);
62
- } catch {
63
- throw new Error(
64
- 'MCP_REST_ACTIONS must be valid JSON: {"name":{"method":"POST","url":"http://host/path","bearerToken":"..."}}'
65
- );
66
- }
67
-
68
- if (typeof json !== 'object' || json === null || Array.isArray(json)) {
69
- throw new Error('MCP_REST_ACTIONS must be a JSON object keyed by action name');
70
- }
71
-
72
- // Null-prototype output: even a hostile config key can never shadow or reach
73
- // Object.prototype members, and runtime lookups stay own-property-only.
74
- const actions: Record<string, RestAction> = Object.create(null) as Record<string, RestAction>;
75
-
76
- for (const [name, value] of Object.entries(json)) {
77
- if (!ACTION_NAME_PATTERN.test(name)) {
78
- throw new Error(
79
- `Invalid MCP_REST_ACTIONS action name '${name}': must match ${ACTION_NAME_PATTERN.source}`
80
- );
81
- }
82
- const parsed = actionSchema.safeParse(value);
83
- if (!parsed.success) {
84
- const messages = parsed.error.issues.map((issue) => issue.message);
85
- throw new Error(`Invalid MCP_REST_ACTIONS entry '${name}':\n${messages.join('\n')}`);
86
- }
87
- const url = new URL(parsed.data.url);
88
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
89
- throw new Error(`Invalid MCP_REST_ACTIONS entry '${name}': only http(s) urls are allowed`);
90
- }
91
- actions[name] = parsed.data;
92
- }
93
-
94
- return actions;
95
- }
@@ -1,138 +0,0 @@
1
- /**
2
- * Infra tools — pre-registered REST actions ("actuator rails for HTTP").
3
- *
4
- * Design rules:
5
- * - The healer lives OUTSIDE the patient (2026-07-10 v1.5.5 outage lesson):
6
- * this server never holds a docker socket or update logic; it only pokes
7
- * separately-running endpoints (watchtower HTTP API, webhooks) that keep
8
- * their own privileges.
9
- * - The model can only NAME an action, never shape a request: exact-match,
10
- * own-property lookup into operator config. No model string reaches the
11
- * URL, method, headers, or body. Redirects are refused; responses are
12
- * truncated before they re-enter the context window.
13
- */
14
-
15
- import type { McpServer } from '@modelcontextprotocol/server';
16
- import { z } from 'zod';
17
-
18
- import { toToolResult, wrapToolHandler, Permission } from '../common.js';
19
- import { logger } from '../../utils/logger.js';
20
-
21
- import type { RestAction } from './actions.js';
22
-
23
- export type { RestAction } from './actions.js';
24
- export { parseRestActions } from './actions.js';
25
-
26
- /** Default per-action timeout; watchtower update cycles pull images. */
27
- const DEFAULT_TIMEOUT_MS = 120_000;
28
- /** Cap on response bytes surfaced to the caller (anti context-flooding). */
29
- const MAX_RESPONSE_CHARS = 500;
30
- /** Default per-action cooldown (override per action via `cooldownMs`; 0 disables). */
31
- const DEFAULT_COOLDOWN_MS = 60_000;
32
-
33
- /**
34
- * Last-fired timestamps per action name — the rate limiter's whole state.
35
- *
36
- * MODULE-level on purpose: the HTTP server registers tools per SESSION, so a
37
- * closure-scoped map would give every new session a fresh cooldown (trivially
38
- * bypassed by reconnecting). One process = one store. In-memory is correct for
39
- * this single-process server; the check-and-claim below runs synchronously
40
- * before any await, so two racing calls cannot both pass (no TOCTOU on the
41
- * event loop). Resets on restart — acceptable: a restart itself takes longer
42
- * than the default cooldown.
43
- */
44
- const lastFired = new Map<string, number>();
45
-
46
- /** Test hook: clear the process-wide cooldown state between test cases. */
47
- export function resetActionCooldowns(): void {
48
- lastFired.clear();
49
- }
50
-
51
- export function registerInfraTools(server: McpServer, actions: Record<string, RestAction>): number {
52
- const names = Object.keys(actions);
53
- if (names.length === 0) {
54
- return 0;
55
- }
56
-
57
- const catalogue = names
58
- .map((n) => {
59
- const description = actions[n].description;
60
- return description ? `${n} (${description})` : n;
61
- })
62
- .join('; ');
63
-
64
- const inputSchema = z.object({
65
- action: z
66
- .string()
67
- .min(1)
68
- .max(64, 'action names are at most 64 chars')
69
- .describe(`Name of the pre-registered action to invoke — one of: ${names.join(', ')}`),
70
- });
71
-
72
- server.registerTool(
73
- 'invokeAction',
74
- {
75
- description:
76
- 'Invoke a pre-registered REST action by name. Actions are fixed, operator-configured HTTP calls ' +
77
- '(method, URL, and credentials come from server config — only the name is chosen here). ' +
78
- `Available actions: ${catalogue}. ` +
79
- 'Actions have real-world effects (deploys, restarts, webhooks) - confirm with the user before applying.',
80
- inputSchema: inputSchema,
81
- },
82
- wrapToolHandler('invokeAction', async ({ action }) => {
83
- // Own-property, exact-match lookup. The registry has a null prototype,
84
- // so names like 'constructor' or '__proto__' can never resolve to
85
- // anything but a genuinely configured action.
86
- if (!Object.hasOwn(actions, action)) {
87
- throw new Error(`Unknown action '${action}'. Available actions: ${names.join(', ')}`);
88
- }
89
- const spec = actions[action];
90
-
91
- // Rate limit: check-and-CLAIM synchronously before the fetch, so the
92
- // slot is consumed even when the call fails (no hammering a dying
93
- // endpoint), and two racing calls can't both pass.
94
- const cooldownMs = spec.cooldownMs ?? DEFAULT_COOLDOWN_MS;
95
- if (cooldownMs > 0) {
96
- const now = Date.now();
97
- const elapsed = now - (lastFired.get(action) ?? Number.NEGATIVE_INFINITY);
98
- if (elapsed < cooldownMs) {
99
- const retryInS = Math.ceil((cooldownMs - elapsed) / 1000);
100
- throw new Error(
101
- `Action '${action}' is cooling down — try again in ~${String(retryInS)}s. ` +
102
- 'Repeated firing of infrastructure actions is rate-limited by design.'
103
- );
104
- }
105
- lastFired.set(action, now);
106
- }
107
-
108
- logger.info('Invoking registered REST action', { action, method: spec.method, url: spec.url });
109
-
110
- const response = await fetch(spec.url, {
111
- method: spec.method,
112
- headers: spec.bearerToken ? { Authorization: `Bearer ${spec.bearerToken}` } : undefined,
113
- // A registered endpoint that suddenly redirects is a surprise, not
114
- // a convenience — fail closed rather than follow.
115
- redirect: 'error',
116
- signal: AbortSignal.timeout(spec.timeoutMs ?? DEFAULT_TIMEOUT_MS),
117
- });
118
-
119
- const body = (await response.text().catch(() => '')).slice(0, MAX_RESPONSE_CHARS);
120
-
121
- if (!response.ok) {
122
- throw new Error(`Action '${action}' returned HTTP ${String(response.status)}${body ? `: ${body}` : ''}`);
123
- }
124
-
125
- return toToolResult({
126
- action,
127
- status: 'completed',
128
- httpStatus: response.status,
129
- response: body || undefined,
130
- });
131
- },
132
- Permission.ADMIN
133
- )
134
- );
135
-
136
- logger.info('Infra tools registered', { toolCount: 1, actions: names });
137
- return 1;
138
- }
@@ -1,28 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import { z } from 'zod';
3
-
4
- import type { OmadaClient } from '../../omadaClient/index.js';
5
- import { toToolResult, wrapToolHandler, Permission } from '../common.js';
6
-
7
- export function registerBlockClientTool(server: McpServer, client: OmadaClient): void {
8
- const inputSchema = z.object({
9
- clientMac: z.string().min(1, 'clientMac (MAC address) is required'),
10
- siteId: z.string().min(1).optional(),
11
- });
12
-
13
- server.registerTool(
14
- 'omada_blockClient',
15
- {
16
- description:
17
- 'Block a client from the network by MAC address. The client is denied all network access until unblocked. ' +
18
- 'Use omada_listClients first to find the exact MAC. This affects real network connectivity - confirm with the user before applying. ' +
19
- "Caution: a blocked device cannot reconnect, and once the controller stops treating it as current, Omada's API can no longer unblock it. " +
20
- 'For recurring schedules (e.g. a curfew) prefer an Omada time-range access rule on the controller over daily block/unblock calls.',
21
- inputSchema: inputSchema,
22
- },
23
- wrapToolHandler('omada_blockClient', async ({ clientMac, siteId }) =>
24
- toToolResult(await client.blockClient(clientMac, siteId)),
25
- Permission.CONTROL
26
- )
27
- );
28
- }
@@ -1,32 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import { z } from 'zod';
3
-
4
- import type { OmadaClient } from '../../omadaClient/index.js';
5
- import { toToolResult, wrapToolHandler, Permission } from '../common.js';
6
-
7
- export function registerCyclePoePortTool(server: McpServer, client: OmadaClient): void {
8
- const inputSchema = z.object({
9
- switchMac: z.string().min(1, 'switchMac (MAC address of the Omada switch) is required'),
10
- ports: z.array(z.number().int().positive()).min(1, 'at least one port number is required'),
11
- siteId: z.string().min(1).optional(),
12
- });
13
-
14
- server.registerTool(
15
- 'omada_cyclePoePort',
16
- {
17
- description:
18
- 'Power-cycle PoE on one or more switch ports — a remote hard-reboot for whatever the ports power ' +
19
- '(access points, cameras, PoE sensors). The switch briefly cuts and restores power on ONLY the given ' +
20
- 'ports; the switch itself and other ports are unaffected. Use omada_listDevices to find the switch MAC ' +
21
- 'and identify which port powers which device first. The powered device goes down for the cycle - ' +
22
- 'confirm with the user before applying.',
23
- inputSchema: inputSchema,
24
- },
25
- wrapToolHandler('omada_cyclePoePort', async ({ switchMac, ports, siteId }) => {
26
- await client.cyclePoePorts(switchMac, ports, siteId);
27
- return toToolResult({ switchMac, ports, status: 'poe-recovery triggered' });
28
- },
29
- Permission.CONTROL
30
- )
31
- );
32
- }
@@ -1,27 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import { z } from 'zod';
3
-
4
- import type { OmadaClient } from '../../omadaClient/index.js';
5
- import { toToolResult, wrapToolHandler, Permission } from '../common.js';
6
-
7
- export function registerDeleteClientTool(server: McpServer, client: OmadaClient): void {
8
- const inputSchema = z.object({
9
- clientMac: z.string().min(1, 'clientMac (MAC address) is required'),
10
- siteId: z.string().min(1).optional(),
11
- });
12
-
13
- server.registerTool(
14
- 'omada_deleteClient',
15
- {
16
- description:
17
- "Delete the controller's record of a client by MAC: its name, history and block state. " +
18
- 'Use it to clear a block that omada_unblockClient can no longer reach (the device stays refused by the access points although Omada lists nothing). ' +
19
- 'The device is treated as new when it reconnects. Irreversible for that history - confirm with the user before applying.',
20
- inputSchema,
21
- },
22
- wrapToolHandler('omada_deleteClient', async ({ clientMac, siteId }: z.infer<typeof inputSchema>) =>
23
- toToolResult(await client.deleteClient(clientMac, siteId)),
24
- Permission.CONFIGURE
25
- )
26
- );
27
- }
@@ -1,23 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import { z } from 'zod';
3
- import type { OmadaClient } from '../../omadaClient/index.js';
4
- import { toToolResult, wrapToolHandler, Permission } from '../common.js';
5
-
6
- export function registerDisableClientRateLimitTool(server: McpServer, client: OmadaClient): void {
7
- const inputSchema = z.object({
8
- clientMac: z.string().min(1, 'clientMac (MAC address) is required'),
9
- siteId: z.string().min(1).optional(),
10
- });
11
-
12
- server.registerTool(
13
- 'omada_disableClientRateLimit',
14
- {
15
- description: 'Remove bandwidth limits from a client',
16
- inputSchema: inputSchema,
17
- },
18
- wrapToolHandler('omada_disableClientRateLimit', async ({ clientMac, siteId }) =>
19
- toToolResult(await client.disableClientRateLimit(clientMac, siteId)),
20
- Permission.CONTROL
21
- )
22
- );
23
- }
@@ -1,14 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import type { OmadaClient } from '../../omadaClient/index.js';
3
- import { clientIdSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
4
-
5
- export function registerGetClientTool(server: McpServer, client: OmadaClient): void {
6
- server.registerTool(
7
- 'omada_getClient',
8
- {
9
- description: 'Get detailed information about a specific connected client',
10
- inputSchema: clientIdSchema,
11
- },
12
- wrapToolHandler('omada_getClient', async ({ clientId, siteId }) => toToolResult(await client.getClient(clientId, siteId)), Permission.QUERY)
13
- );
14
- }
@@ -1,14 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import type { OmadaClient } from '../../omadaClient/index.js';
3
- import { deviceIdSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
4
-
5
- export function registerGetDeviceTool(server: McpServer, client: OmadaClient): void {
6
- server.registerTool(
7
- 'omada_getDevice',
8
- {
9
- description: 'Get detailed information about a specific network device',
10
- inputSchema: deviceIdSchema,
11
- },
12
- wrapToolHandler('omada_getDevice', async ({ deviceId, siteId }) => toToolResult(await client.getDevice(deviceId, siteId)), Permission.QUERY)
13
- );
14
- }
@@ -1,14 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import type { OmadaClient } from '../../omadaClient/index.js';
3
- import { siteInputSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
4
-
5
- export function registerGetFirewallSettingTool(server: McpServer, client: OmadaClient): void {
6
- server.registerTool(
7
- 'omada_getFirewallSetting',
8
- {
9
- description: 'Get firewall configuration for a site',
10
- inputSchema: siteInputSchema,
11
- },
12
- wrapToolHandler('omada_getFirewallSetting', async ({ siteId }) => toToolResult(await client.getFirewallSetting(siteId)), Permission.QUERY)
13
- );
14
- }
@@ -1,14 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import type { OmadaClient } from '../../omadaClient/index.js';
3
- import { siteInputSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
4
-
5
- export function registerGetInternetInfoTool(server: McpServer, client: OmadaClient): void {
6
- server.registerTool(
7
- 'omada_getInternetInfo',
8
- {
9
- description: 'Get internet connection configuration for a site',
10
- inputSchema: siteInputSchema,
11
- },
12
- wrapToolHandler('omada_getInternetInfo', async ({ siteId }) => toToolResult(await client.getInternetInfo(siteId)), Permission.QUERY)
13
- );
14
- }
@@ -1,14 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import type { OmadaClient } from '../../omadaClient/index.js';
3
- import { siteInputSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
4
-
5
- export function registerGetLanNetworkListTool(server: McpServer, client: OmadaClient): void {
6
- server.registerTool(
7
- 'omada_getLanNetworkList',
8
- {
9
- description: 'Get LAN network configuration list',
10
- inputSchema: siteInputSchema,
11
- },
12
- wrapToolHandler('omada_getLanNetworkList', async ({ siteId }) => toToolResult(await client.getLanNetworkList(siteId)), Permission.QUERY)
13
- );
14
- }
@@ -1,14 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import type { OmadaClient } from '../../omadaClient/index.js';
3
- import { siteInputSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
4
-
5
- export function registerGetLanProfileListTool(server: McpServer, client: OmadaClient): void {
6
- server.registerTool(
7
- 'omada_getLanProfileList',
8
- {
9
- description: 'Get LAN profile configuration list',
10
- inputSchema: siteInputSchema,
11
- },
12
- wrapToolHandler('omada_getLanProfileList', async ({ siteId }) => toToolResult(await client.getLanProfileList(siteId)), Permission.QUERY)
13
- );
14
- }
@@ -1,26 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import { z } from 'zod';
3
-
4
- import type { OmadaClient } from '../../omadaClient/index.js';
5
- import { toToolResult, wrapToolHandler, Permission } from '../common.js';
6
- import { createPaginationSchema } from '../utils/pagination-schema.js';
7
-
8
- const portForwardingSchema = z.object({
9
- type: z.enum(['User', 'UPnP']).describe('Port forwarding type: User (manually configured) or UPnP (automatically configured)'),
10
- siteId: z.string().min(1).describe('Site ID (required)'),
11
- ...createPaginationSchema(10),
12
- });
13
-
14
- export function registerGetPortForwardingStatusTool(server: McpServer, client: OmadaClient): void {
15
- server.registerTool(
16
- 'omada_getPortForwardingStatus',
17
- {
18
- description: 'Get port forwarding rules (User or UPnP)',
19
- inputSchema: portForwardingSchema,
20
- },
21
- wrapToolHandler('omada_getPortForwardingStatus', async ({ type, siteId, page = 1, pageSize = 10 }) =>
22
- toToolResult(await client.getPortForwardingStatus(type, siteId, page, pageSize)),
23
- Permission.QUERY
24
- )
25
- );
26
- }
@@ -1,14 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import type { OmadaClient } from '../../omadaClient/index.js';
3
- import { siteInputSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
4
-
5
- export function registerGetRateLimitProfilesTool(server: McpServer, client: OmadaClient): void {
6
- server.registerTool(
7
- 'omada_getRateLimitProfiles',
8
- {
9
- description: 'Get available rate limit profiles for a site',
10
- inputSchema: siteInputSchema,
11
- },
12
- wrapToolHandler('omada_getRateLimitProfiles', async ({ siteId }) => toToolResult(await client.getRateLimitProfiles(siteId)), Permission.QUERY)
13
- );
14
- }
@@ -1,14 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import type { OmadaClient } from '../../omadaClient/index.js';
3
- import { siteInputSchema, toToolResult, wrapToolHandler, Permission } from '../common.js';
4
-
5
- export function registerGetSiteNtpStatusTool(server: McpServer, client: OmadaClient): void {
6
- server.registerTool(
7
- 'omada_getSiteNtpStatus',
8
- {
9
- description: 'Get the NTP (network time) server configuration and status for a site',
10
- inputSchema: siteInputSchema,
11
- },
12
- wrapToolHandler('omada_getSiteNtpStatus', async ({ siteId }) => toToolResult(await client.getSiteNtpStatus(siteId)), Permission.QUERY)
13
- );
14
- }