@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,58 +0,0 @@
1
- /**
2
- * Update Operations for Home Assistant API
3
- * Handles checking for available updates
4
- */
5
-
6
- import type { Entity, UpdateInfo } from '../types/index.js';
7
- import { logger } from '../utils/logger.js';
8
- import type { EntityOperations } from './entities.js';
9
-
10
- /**
11
- * Update-related operations for the Home Assistant API.
12
- */
13
- export class UpdateOperations {
14
- constructor(private readonly entityOps: EntityOperations) {}
15
-
16
- /**
17
- * Get available updates by checking update.* entities
18
- * Works with regular long-lived access tokens
19
- */
20
- async getAvailableUpdates(): Promise<{
21
- updates: UpdateInfo[];
22
- entities: Entity[];
23
- }> {
24
- logger.debug('Fetching available updates from update entities');
25
-
26
- // Get all update entities
27
- const updateEntities = await this.entityOps.getEntitiesByDomain('update');
28
-
29
- // Filter to only those with updates available (state = 'on')
30
- const updates: UpdateInfo[] = [];
31
-
32
- for (const entity of updateEntities) {
33
- const attrs = entity.attributes;
34
- const installedVersion = attrs.installed_version as string | undefined;
35
- const latestVersion = attrs.latest_version as string | undefined;
36
- const title = attrs.title as string || attrs.friendly_name as string || entity.entity_id;
37
-
38
- // state 'on' means update is available
39
- if (entity.state === 'on' && installedVersion && latestVersion) {
40
- updates.push({
41
- update_type: entity.entity_id.includes('core') ? 'core' :
42
- entity.entity_id.includes('supervisor') ? 'supervisor' :
43
- entity.entity_id.includes('operating_system') ? 'os' : 'addon',
44
- name: title,
45
- version_current: installedVersion,
46
- version_latest: latestVersion,
47
- });
48
- }
49
- }
50
-
51
- logger.info('Fetched available updates', { updateCount: updates.length, totalEntities: updateEntities.length });
52
-
53
- return {
54
- updates,
55
- entities: updateEntities,
56
- };
57
- }
58
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * One-shot Home Assistant WebSocket command: connect, authenticate, send one
3
- * command, return its result, disconnect. For the few HA APIs that exist only
4
- * over WebSocket (e.g. automation traces), usable in every transport mode.
5
- */
6
-
7
- import { EventSubscriber } from './events.js';
8
-
9
- export type HaWebSocketCommand = <T = unknown>(command: { type: string; [key: string]: unknown }) => Promise<T>;
10
-
11
- export function createHaWebSocketCommand(config: {
12
- baseUrl: string;
13
- token: string;
14
- strictSsl?: boolean;
15
- /** One deadline for connecting, authenticating and the command (ms). Default 30 s. */
16
- timeoutMs?: number;
17
- }): HaWebSocketCommand {
18
- const timeoutMs = config.timeoutMs ?? 30000;
19
- return async <T>(command: { type: string; [key: string]: unknown }): Promise<T> => {
20
- const connection = new EventSubscriber({
21
- baseUrl: config.baseUrl,
22
- token: config.token,
23
- strictSsl: config.strictSsl,
24
- maxReconnectAttempts: 0,
25
- connectTimeout: timeoutMs,
26
- });
27
- let timer: ReturnType<typeof setTimeout> | undefined;
28
- const deadline = new Promise<never>((_, reject) => {
29
- timer = setTimeout(() => reject(new Error(`Home Assistant command '${command.type}' timed out`)), timeoutMs);
30
- });
31
- const work = (async () => {
32
- await connection.connect();
33
- return await connection.sendCommand<T>(command, timeoutMs);
34
- })();
35
- // If the deadline wins, the abandoned attempt still settles later: observe it.
36
- work.catch(() => undefined);
37
- try {
38
- return await Promise.race([work, deadline]);
39
- } finally {
40
- clearTimeout(timer);
41
- connection.disconnect();
42
- }
43
- };
44
- }
package/src/index.ts DELETED
@@ -1,164 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Home Assistant MCP Server
4
- * Entry point for the Model Context Protocol server
5
- * Supports transport modes: stdio, HTTP (Streamable HTTP)
6
- */
7
-
8
- import { loadConfig } from './config.js';
9
- import { HaClient } from './haClient/index.js';
10
- import { LocalAIClient } from './localAI/index.js';
11
- import { OmadaClient } from './omadaClient/index.js';
12
- import { checkConfiguredSite } from './omadaClient/site.js';
13
- import { startHttpServer } from './server/http.js';
14
- import { startStdioServer } from './server/stdio.js';
15
- import { initLogger, logger } from './utils/logger.js';
16
- import { VERSION } from './version.js';
17
-
18
- async function main(): Promise<void> {
19
- try {
20
- // Load and validate configuration
21
- const config = loadConfig();
22
-
23
- // When running in stdio mode, logs must go to stderr to avoid interfering with MCP protocol on stdout
24
- const useStderr = !config.useHttp;
25
-
26
- // Initialize logger with configured level, format, and output stream
27
- initLogger(config.logLevel, config.logFormat, useStderr);
28
-
29
- logger.info(`mcp-ha-connect v${VERSION} starting`);
30
- logger.info('Loaded configuration', {
31
- haPluginEnabled: config.haPluginEnabled,
32
- aiPluginEnabled: config.aiPluginEnabled,
33
- omadaPluginEnabled: config.omadaPluginEnabled,
34
- useHttp: config.useHttp,
35
- });
36
-
37
- // Create Home Assistant client (if plugin enabled and configured)
38
- let haClient: HaClient | undefined;
39
- if (config.haPluginEnabled) {
40
- if (config.baseUrl && config.token) {
41
- haClient = new HaClient({
42
- baseUrl: config.baseUrl,
43
- token: config.token,
44
- timeout: config.timeout,
45
- strictSsl: config.strictSsl,
46
- });
47
-
48
- // Test Home Assistant connection
49
- const apiCheck = await haClient.checkApi();
50
- logger.info('Connected to Home Assistant', { message: apiCheck.message });
51
- } else {
52
- logger.warn('HA plugin enabled but HA_URL or HA_TOKEN not set');
53
- }
54
- } else {
55
- logger.info('Home Assistant plugin disabled');
56
- }
57
-
58
- // Create Omada client (if plugin enabled and configured)
59
- let omadaClient: OmadaClient | undefined;
60
- if (config.omadaPluginEnabled) {
61
- if (config.omadaBaseUrl && config.omadaClientId && config.omadaClientSecret && config.omadacId) {
62
- try {
63
- omadaClient = new OmadaClient({
64
- baseUrl: config.omadaBaseUrl,
65
- clientId: config.omadaClientId,
66
- clientSecret: config.omadaClientSecret,
67
- omadacId: config.omadacId,
68
- siteId: config.siteId,
69
- strictSsl: config.omadaStrictSsl,
70
- requestTimeout: config.requestTimeout,
71
- });
72
-
73
- // Test Omada connection by listing sites
74
- const sites = await omadaClient.listSites();
75
-
76
- // Omada is the source of truth: a configured OMADA_SITE_ID that isn't
77
- // on the controller is a deployment bug (drifted site id), not a
78
- // transient outage. Fail the Omada plugin loudly — HA and AI keep
79
- // working — instead of silently scoping every read to a dead site.
80
- const siteError = checkConfiguredSite(config.siteId, sites);
81
- if (siteError) {
82
- logger.error(`Omada plugin disabled - invalid OMADA_SITE_ID. ${siteError}`);
83
- omadaClient = undefined;
84
- } else {
85
- logger.info('Connected to Omada controller', { siteCount: sites.length });
86
- }
87
- } catch (error) {
88
- logger.warn('Omada controller not available - Omada tools will be disabled', {
89
- error: error instanceof Error ? error.message : String(error),
90
- });
91
- omadaClient = undefined;
92
- }
93
- } else {
94
- logger.warn('Omada plugin enabled but required config not set (OMADA_BASE_URL, OMADA_CLIENT_ID, OMADA_CLIENT_SECRET, OMADA_OMADAC_ID)');
95
- }
96
- } else {
97
- logger.info('Omada plugin disabled');
98
- }
99
-
100
- // Create Local AI client (if plugin enabled)
101
- let aiClient: LocalAIClient | undefined;
102
- if (config.aiPluginEnabled) {
103
- if (config.aiProvider === 'none') {
104
- logger.info('AI plugin enabled but AI_PROVIDER=none');
105
- } else {
106
- aiClient = new LocalAIClient(config);
107
- const aiHealthy = await aiClient.checkHealth();
108
- if (aiHealthy) {
109
- logger.info('Connected to AI provider', {
110
- provider: aiClient.getProviderName(),
111
- url: config.aiUrl,
112
- model: config.aiModel,
113
- });
114
- } else {
115
- logger.warn('AI provider not available - AI analysis tool will be disabled', {
116
- provider: config.aiProvider,
117
- url: config.aiUrl,
118
- });
119
- aiClient = undefined;
120
- }
121
- }
122
- } else {
123
- logger.info('AI plugin disabled');
124
- }
125
-
126
- // Start server in appropriate mode
127
- if (config.useHttp) {
128
- await startHttpServer({ haClient, omadaClient, aiClient, config });
129
- } else {
130
- await startStdioServer({
131
- haClient,
132
- omadaClient,
133
- aiClient,
134
- restActions: config.restActions,
135
- toolRegistrationMode: config.toolRegistrationMode,
136
- });
137
- }
138
- } catch (error) {
139
- const message = error instanceof Error ? error.message : String(error);
140
- logger.error('Failed to start MCP server', {
141
- error: message,
142
- stack: error instanceof Error ? error.stack : undefined,
143
- });
144
- process.exitCode = 1;
145
- }
146
- }
147
-
148
- // Handle graceful shutdown
149
- process.on('SIGINT', () => {
150
- logger.info('Received SIGINT, shutting down gracefully');
151
- process.exit(0);
152
- });
153
-
154
- process.on('SIGTERM', () => {
155
- logger.info('Received SIGTERM, shutting down gracefully');
156
- process.exit(0);
157
- });
158
-
159
- // Start the server
160
- main().catch((error) => {
161
- const message = error instanceof Error ? error.message : String(error);
162
- logger.error('Unhandled error in main', { error: message });
163
- process.exitCode = 1;
164
- });
@@ -1,88 +0,0 @@
1
- /**
2
- * Local AI Client
3
- * Factory and facade for AI providers
4
- */
5
-
6
- import { logger } from '../utils/logger.js';
7
- import type { EnvironmentConfig } from '../config.js';
8
- import type { AIProviderConfig, AIProviderInterface, AnalysisResult, SensorData } from './types.js';
9
- import { OllamaProvider } from './providers/ollama.js';
10
- import { OpenAICompatibleProvider } from './providers/openai.js';
11
-
12
- // Re-export types for convenience
13
- export type { SensorData, AnalysisResult, AIProviderType } from './types.js';
14
-
15
- /**
16
- * Create the appropriate AI provider based on configuration
17
- */
18
- function createProvider(providerConfig: AIProviderConfig): AIProviderInterface {
19
- switch (providerConfig.provider) {
20
- case 'openai':
21
- return new OpenAICompatibleProvider(providerConfig);
22
- case 'ollama':
23
- default:
24
- return new OllamaProvider(providerConfig);
25
- }
26
- }
27
-
28
- /**
29
- * Local AI Client
30
- * Facade for AI analysis using configurable providers
31
- */
32
- export class LocalAIClient {
33
- private provider: AIProviderInterface;
34
- private providerConfig: AIProviderConfig;
35
-
36
- constructor(config: EnvironmentConfig) {
37
- this.providerConfig = {
38
- provider: config.aiProvider,
39
- baseUrl: config.aiUrl,
40
- model: config.aiModel,
41
- timeout: config.aiTimeout,
42
- apiKey: config.aiApiKey,
43
- };
44
-
45
- this.provider = createProvider(this.providerConfig);
46
-
47
- logger.info('LocalAIClient initialized', {
48
- provider: this.providerConfig.provider,
49
- model: this.providerConfig.model,
50
- url: this.providerConfig.baseUrl,
51
- });
52
- }
53
-
54
- /**
55
- * Analyze sensor data using AI
56
- */
57
- async analyzeSensors(sensors: SensorData): Promise<AnalysisResult> {
58
- return this.provider.analyzeSensors(sensors);
59
- }
60
-
61
- /**
62
- * Check if the AI provider is available
63
- */
64
- async checkHealth(): Promise<boolean> {
65
- return this.provider.checkHealth();
66
- }
67
-
68
- /**
69
- * Get the provider name
70
- */
71
- getProviderName(): string {
72
- return this.provider.getProviderName();
73
- }
74
-
75
- /**
76
- * Get the configured model name
77
- */
78
- getModelName(): string {
79
- return this.providerConfig.model;
80
- }
81
-
82
- /**
83
- * Get the provider configuration
84
- */
85
- getConfig(): Readonly<AIProviderConfig> {
86
- return { ...this.providerConfig };
87
- }
88
- }
@@ -1,50 +0,0 @@
1
- /**
2
- * AI System Prompts
3
- * Shared prompts for sensor analysis across all providers
4
- */
5
-
6
- /**
7
- * System prompt for sensor analysis
8
- */
9
- export const SENSOR_ANALYSIS_SYSTEM_PROMPT = `You are a home automation AI analyzing Home Assistant sensors.
10
-
11
- SEVERITY RULES:
12
- - critical: Gas/smoke/CO detected, fire, temp>40°C indoors, power spike>500%, door open>2h with alarm
13
- - high: Battery<10%, power spike>300%, door open>30min with alarm, temp>35°C
14
- - medium: Battery<20%, connectivity issues, minor anomalies
15
- - low: Informational warnings
16
-
17
- OUTPUT FORMAT (JSON only, no markdown):
18
- {
19
- "severity": "critical|high|medium|low",
20
- "issue": "brief description",
21
- "reasoning": "detailed explanation",
22
- "recommended_actions": ["action1", "action2"],
23
- "ssh_diagnostics": ["command1", "command2"],
24
- "confidence": 0.85
25
- }
26
-
27
- Respond with JSON only. No extra text.`;
28
-
29
- /**
30
- * Build the full prompt for sensor analysis
31
- */
32
- export function buildSensorAnalysisPrompt(sensors: Record<string, unknown>): string {
33
- return `${SENSOR_ANALYSIS_SYSTEM_PROMPT}\n\nAnalyze these sensors: ${JSON.stringify(sensors)}\n\nProvide JSON only:`;
34
- }
35
-
36
- /**
37
- * Build messages array for OpenAI-compatible APIs
38
- */
39
- export function buildChatMessages(sensors: Record<string, unknown>): Array<{ role: string; content: string }> {
40
- return [
41
- {
42
- role: 'system',
43
- content: SENSOR_ANALYSIS_SYSTEM_PROMPT,
44
- },
45
- {
46
- role: 'user',
47
- content: `Analyze these sensors: ${JSON.stringify(sensors)}\n\nProvide JSON only:`,
48
- },
49
- ];
50
- }
@@ -1,120 +0,0 @@
1
- /**
2
- * Base AI Provider
3
- * Abstract class with shared logic for all AI providers
4
- */
5
-
6
- import { logger } from '../../utils/logger.js';
7
- import type { AIProviderConfig, AIProviderInterface, AnalysisResult, SensorData } from '../types.js';
8
-
9
- /**
10
- * Raw analysis result from AI (before adding metadata)
11
- */
12
- export interface RawAnalysisResult {
13
- severity: 'critical' | 'high' | 'medium' | 'low';
14
- issue: string;
15
- reasoning: string;
16
- recommended_actions: string[];
17
- ssh_diagnostics: string[];
18
- confidence: number;
19
- }
20
-
21
- /**
22
- * Abstract base class for AI providers
23
- */
24
- export abstract class BaseAIProvider implements AIProviderInterface {
25
- protected baseUrl: string;
26
- protected model: string;
27
- protected timeout: number;
28
- protected apiKey?: string;
29
-
30
- constructor(config: AIProviderConfig) {
31
- this.baseUrl = config.baseUrl;
32
- this.model = config.model;
33
- this.timeout = config.timeout;
34
- this.apiKey = config.apiKey;
35
- }
36
-
37
- /**
38
- * Get the provider name - must be implemented by subclasses
39
- */
40
- abstract getProviderName(): string;
41
-
42
- /**
43
- * Make the actual API request - must be implemented by subclasses
44
- */
45
- protected abstract makeRequest(sensors: SensorData): Promise<string>;
46
-
47
- /**
48
- * Check health - must be implemented by subclasses
49
- */
50
- abstract checkHealth(): Promise<boolean>;
51
-
52
- /**
53
- * Analyze sensors using AI
54
- */
55
- async analyzeSensors(sensors: SensorData): Promise<AnalysisResult> {
56
- const sensorCount = Object.keys(sensors).length;
57
- const providerName = this.getProviderName();
58
-
59
- logger.info('Starting AI analysis', { sensorCount, model: this.model, provider: providerName });
60
-
61
- const startTime = Date.now();
62
-
63
- try {
64
- const aiText = await this.makeRequest(sensors);
65
-
66
- if (!aiText) {
67
- throw new Error(`Empty response from ${providerName}`);
68
- }
69
-
70
- // Extract JSON from response (handles potential text around JSON)
71
- const analysis = this.extractJson(aiText);
72
- const elapsed = (Date.now() - startTime) / 1000;
73
-
74
- logger.info('AI analysis complete', {
75
- severity: analysis.severity,
76
- elapsed: `${elapsed.toFixed(2)}s`,
77
- model: this.model,
78
- provider: providerName,
79
- });
80
-
81
- return {
82
- ...analysis,
83
- model: this.model,
84
- provider: providerName,
85
- response_time: parseFloat(elapsed.toFixed(2)),
86
- };
87
- } catch (error) {
88
- const elapsed = (Date.now() - startTime) / 1000;
89
- logger.error('AI analysis failed', {
90
- error: error instanceof Error ? error.message : String(error),
91
- elapsed: `${elapsed.toFixed(2)}s`,
92
- provider: providerName,
93
- });
94
- throw error;
95
- }
96
- }
97
-
98
- /**
99
- * Extract JSON from AI response text
100
- */
101
- protected extractJson(text: string): RawAnalysisResult {
102
- // Try to find JSON object in the response
103
- const jsonMatch = text.match(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/);
104
- if (!jsonMatch) {
105
- throw new Error(`No JSON found in AI response: ${text.substring(0, 200)}`);
106
- }
107
-
108
- return JSON.parse(jsonMatch[0]) as RawAnalysisResult;
109
- }
110
-
111
- /**
112
- * Build authorization headers if API key is set
113
- */
114
- protected getAuthHeaders(): Record<string, string> {
115
- if (this.apiKey) {
116
- return { Authorization: `Bearer ${this.apiKey}` };
117
- }
118
- return {};
119
- }
120
- }
@@ -1,78 +0,0 @@
1
- /**
2
- * Ollama AI Provider
3
- * Native Ollama API implementation
4
- */
5
-
6
- import type { AIProviderConfig, SensorData } from '../types.js';
7
- import { buildSensorAnalysisPrompt } from '../prompts.js';
8
- import { BaseAIProvider } from './base.js';
9
-
10
- /**
11
- * Ollama API response format
12
- */
13
- interface OllamaResponse {
14
- response?: string;
15
- error?: string;
16
- }
17
-
18
- /**
19
- * Ollama AI provider using native Ollama API
20
- */
21
- export class OllamaProvider extends BaseAIProvider {
22
- constructor(config: AIProviderConfig) {
23
- super(config);
24
- }
25
-
26
- getProviderName(): string {
27
- return 'ollama';
28
- }
29
-
30
- /**
31
- * Make request to Ollama API
32
- */
33
- protected async makeRequest(sensors: SensorData): Promise<string> {
34
- const prompt = buildSensorAnalysisPrompt(sensors);
35
-
36
- const response = await fetch(`${this.baseUrl}/api/generate`, {
37
- method: 'POST',
38
- headers: {
39
- 'Content-Type': 'application/json',
40
- ...this.getAuthHeaders(),
41
- },
42
- body: JSON.stringify({
43
- model: this.model,
44
- prompt,
45
- stream: false,
46
- options: {
47
- temperature: 0.3,
48
- num_predict: 2000,
49
- num_ctx: 4096,
50
- },
51
- }),
52
- signal: AbortSignal.timeout(this.timeout),
53
- });
54
-
55
- if (!response.ok) {
56
- const errorText = await response.text();
57
- throw new Error(`Ollama request failed: ${response.status} ${errorText}`);
58
- }
59
-
60
- const result = (await response.json()) as OllamaResponse;
61
- return result.response ?? '';
62
- }
63
-
64
- /**
65
- * Check if Ollama is available
66
- */
67
- async checkHealth(): Promise<boolean> {
68
- try {
69
- const response = await fetch(`${this.baseUrl}/api/tags`, {
70
- headers: this.getAuthHeaders(),
71
- signal: AbortSignal.timeout(5000),
72
- });
73
- return response.ok;
74
- } catch {
75
- return false;
76
- }
77
- }
78
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * OpenAI-Compatible AI Provider
3
- * Works with LocalAI, LM Studio, vLLM, and any OpenAI-compatible API
4
- */
5
-
6
- import type { AIProviderConfig, SensorData } from '../types.js';
7
- import { buildChatMessages } from '../prompts.js';
8
- import { BaseAIProvider } from './base.js';
9
-
10
- /**
11
- * OpenAI chat completion response format
12
- */
13
- interface OpenAIChatResponse {
14
- choices?: Array<{
15
- message?: {
16
- content?: string;
17
- };
18
- }>;
19
- error?: {
20
- message?: string;
21
- };
22
- }
23
-
24
- /**
25
- * OpenAI-compatible AI provider
26
- * Works with LocalAI, LM Studio, vLLM, and other OpenAI-compatible APIs
27
- */
28
- export class OpenAICompatibleProvider extends BaseAIProvider {
29
- constructor(config: AIProviderConfig) {
30
- super(config);
31
- }
32
-
33
- getProviderName(): string {
34
- return 'openai-compatible';
35
- }
36
-
37
- /**
38
- * Make request to OpenAI-compatible API
39
- */
40
- protected async makeRequest(sensors: SensorData): Promise<string> {
41
- const messages = buildChatMessages(sensors);
42
-
43
- const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
44
- method: 'POST',
45
- headers: {
46
- 'Content-Type': 'application/json',
47
- ...this.getAuthHeaders(),
48
- },
49
- body: JSON.stringify({
50
- model: this.model,
51
- messages,
52
- temperature: 0.3,
53
- max_tokens: 2000,
54
- }),
55
- signal: AbortSignal.timeout(this.timeout),
56
- });
57
-
58
- if (!response.ok) {
59
- const errorText = await response.text();
60
- throw new Error(`OpenAI-compatible API request failed: ${response.status} ${errorText}`);
61
- }
62
-
63
- const result = (await response.json()) as OpenAIChatResponse;
64
-
65
- if (result.error) {
66
- throw new Error(`API error: ${result.error.message}`);
67
- }
68
-
69
- return result.choices?.[0]?.message?.content ?? '';
70
- }
71
-
72
- /**
73
- * Check if the OpenAI-compatible API is available
74
- */
75
- async checkHealth(): Promise<boolean> {
76
- try {
77
- const response = await fetch(`${this.baseUrl}/v1/models`, {
78
- headers: this.getAuthHeaders(),
79
- signal: AbortSignal.timeout(5000),
80
- });
81
- return response.ok;
82
- } catch {
83
- return false;
84
- }
85
- }
86
- }