@coffeerunhobby/mcp-ha-connect 2.4.4 → 2.4.6

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 (40) hide show
  1. package/README.md +22 -1
  2. package/dist/index.js +4 -96
  3. package/dist/index.js.map +1 -1
  4. package/dist/net/adapters/omada.d.ts +71 -0
  5. package/dist/net/adapters/omada.js +865 -0
  6. package/dist/net/adapters/omada.js.map +1 -0
  7. package/dist/net/model.d.ts +415 -0
  8. package/dist/net/model.js +26 -0
  9. package/dist/net/model.js.map +1 -0
  10. package/dist/net/validate.d.ts +22 -0
  11. package/dist/net/validate.js +87 -0
  12. package/dist/net/validate.js.map +1 -0
  13. package/dist/omadaClient/accessControl.d.ts +7 -0
  14. package/dist/omadaClient/accessControl.js +43 -17
  15. package/dist/omadaClient/accessControl.js.map +1 -1
  16. package/dist/omadaClient/index.d.ts +9 -0
  17. package/dist/omadaClient/index.js +15 -0
  18. package/dist/omadaClient/index.js.map +1 -1
  19. package/dist/omadaClient/networkConfig.d.ts +54 -0
  20. package/dist/omadaClient/networkConfig.js +79 -0
  21. package/dist/omadaClient/networkConfig.js.map +1 -0
  22. package/dist/omadaClient/request.d.ts +6 -1
  23. package/dist/omadaClient/request.js +16 -6
  24. package/dist/omadaClient/request.js.map +1 -1
  25. package/dist/tools/index.js +4 -0
  26. package/dist/tools/index.js.map +1 -1
  27. package/dist/tools/net/index.d.ts +125 -0
  28. package/dist/tools/net/index.js +193 -0
  29. package/dist/tools/net/index.js.map +1 -0
  30. package/dist/tools/omada/accessControl.js +1 -0
  31. package/dist/tools/omada/accessControl.js.map +1 -1
  32. package/dist/upstreams.d.ts +47 -0
  33. package/dist/upstreams.js +194 -0
  34. package/dist/upstreams.js.map +1 -0
  35. package/dist/utils/ipv4.d.ts +14 -0
  36. package/dist/utils/ipv4.js +37 -0
  37. package/dist/utils/ipv4.js.map +1 -0
  38. package/dist/version.d.ts +1 -1
  39. package/dist/version.js +1 -1
  40. package/package.json +1 -1
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Connects to the upstreams (Home Assistant, Omada, the local AI) at startup.
3
+ *
4
+ * An upstream that doesn't answer at startup is not fatal: its client is kept, its
5
+ * tools stay registered (calls fail cleanly until it answers) and a background probe
6
+ * retries with a growing pause, logging when it answers. Before 2.4.6 an unreachable
7
+ * Home Assistant made the server exit, and the container crash-looped (100 restarts
8
+ * during the 2026-10-17 NAS network outage) instead of serving /health and Omada.
9
+ *
10
+ * What stays fatal or disabling is configuration that can never work: a missing
11
+ * HA_URL/HA_TOKEN or Omada setting, or an OMADA_SITE_ID the controller doesn't have.
12
+ */
13
+ import { HaClient } from './haClient/index.js';
14
+ import { LocalAIClient } from './localAI/index.js';
15
+ import { OmadaClient } from './omadaClient/index.js';
16
+ import { checkConfiguredSite } from './omadaClient/site.js';
17
+ import { logger } from './utils/logger.js';
18
+ export const DEFAULT_BACKOFF = { initialMs: 5_000, maxMs: 60_000 };
19
+ /**
20
+ * Runs `attempt` until it resolves, pausing initialMs, then twice that, and so on up
21
+ * to maxMs between tries. Failures are logged at debug level only (the caller logs
22
+ * the first one); the timer doesn't keep the process alive.
23
+ */
24
+ export function retryInBackground(name, attempt, backoff = DEFAULT_BACKOFF) {
25
+ let delay = backoff.initialMs;
26
+ let timer;
27
+ let stopped = false;
28
+ const schedule = () => {
29
+ timer = setTimeout(() => void run(), delay);
30
+ timer.unref?.();
31
+ };
32
+ const run = async () => {
33
+ if (stopped)
34
+ return;
35
+ try {
36
+ await attempt();
37
+ }
38
+ catch (error) {
39
+ if (stopped)
40
+ return;
41
+ logger.debug(`${name} still not reachable; next try in ${Math.round(Math.min(delay * 2, backoff.maxMs) / 1000)}s`, {
42
+ error: errorMessage(error),
43
+ });
44
+ delay = Math.min(delay * 2, backoff.maxMs);
45
+ schedule();
46
+ }
47
+ };
48
+ schedule();
49
+ return {
50
+ stop() {
51
+ stopped = true;
52
+ if (timer)
53
+ clearTimeout(timer);
54
+ },
55
+ };
56
+ }
57
+ const defaultFactories = {
58
+ ha: (config) => new HaClient({
59
+ baseUrl: config.baseUrl,
60
+ token: config.token,
61
+ timeout: config.timeout,
62
+ strictSsl: config.strictSsl,
63
+ }),
64
+ omada: (config) => new OmadaClient({
65
+ baseUrl: config.omadaBaseUrl,
66
+ clientId: config.omadaClientId,
67
+ clientSecret: config.omadaClientSecret,
68
+ omadacId: config.omadacId,
69
+ siteId: config.siteId,
70
+ strictSsl: config.omadaStrictSsl,
71
+ requestTimeout: config.requestTimeout,
72
+ }),
73
+ ai: (config) => new LocalAIClient(config),
74
+ };
75
+ /** What an Omada call says once a background check found OMADA_SITE_ID missing (details in the log). */
76
+ export const OMADA_SITE_DISABLED_MESSAGE = 'Omada tools are disabled: OMADA_SITE_ID is not a site on the controller. The server log lists the valid sites.';
77
+ export async function connectUpstreams(config, factories = defaultFactories, backoff = DEFAULT_BACKOFF) {
78
+ const probes = [];
79
+ const upstreams = {
80
+ stop: () => probes.forEach((p) => p.stop()),
81
+ };
82
+ // Home Assistant
83
+ if (config.haPluginEnabled) {
84
+ if (config.baseUrl && config.token) {
85
+ const haClient = factories.ha(config);
86
+ upstreams.haClient = haClient;
87
+ const check = async () => {
88
+ const apiCheck = await haClient.checkApi();
89
+ logger.info('Connected to Home Assistant', { message: apiCheck.message });
90
+ };
91
+ try {
92
+ await check();
93
+ }
94
+ catch (error) {
95
+ logger.warn('Home Assistant not reachable; starting without it and retrying in the background', {
96
+ error: errorMessage(error),
97
+ });
98
+ probes.push(retryInBackground('Home Assistant', check, backoff));
99
+ }
100
+ }
101
+ else {
102
+ logger.warn('HA plugin enabled but HA_URL or HA_TOKEN not set');
103
+ }
104
+ }
105
+ else {
106
+ logger.info('Home Assistant plugin disabled');
107
+ }
108
+ // Omada
109
+ if (config.omadaPluginEnabled) {
110
+ if (config.omadaBaseUrl && config.omadaClientId && config.omadaClientSecret && config.omadacId) {
111
+ let omadaClient;
112
+ try {
113
+ omadaClient = factories.omada(config);
114
+ const client = omadaClient;
115
+ // Omada is the source of truth: a configured OMADA_SITE_ID that isn't on the
116
+ // controller is a deployment bug (drifted site id), not a transient outage.
117
+ const checkSite = async () => {
118
+ const sites = await client.listSites();
119
+ const siteError = checkConfiguredSite(config.siteId, sites);
120
+ if (!siteError)
121
+ logger.info('Connected to Omada controller', { siteCount: sites.length });
122
+ return siteError;
123
+ };
124
+ try {
125
+ const siteError = await checkSite();
126
+ if (siteError) {
127
+ // Known at startup: fail the plugin loudly (no Omada tools); HA and AI keep working.
128
+ logger.error(`Omada plugin disabled - invalid OMADA_SITE_ID. ${siteError}`);
129
+ omadaClient = undefined;
130
+ }
131
+ }
132
+ catch (error) {
133
+ logger.warn('Omada controller not reachable; Omada tools stay registered and work once it answers. Retrying in the background', {
134
+ error: errorMessage(error),
135
+ });
136
+ probes.push(retryInBackground('Omada controller', async () => {
137
+ const siteError = await checkSite();
138
+ if (siteError) {
139
+ // Found later: the tools are already registered, so every call now refuses.
140
+ logger.error(`Omada plugin disabled - invalid OMADA_SITE_ID. ${siteError}`);
141
+ client.disable(OMADA_SITE_DISABLED_MESSAGE);
142
+ }
143
+ }, backoff));
144
+ }
145
+ }
146
+ catch (error) {
147
+ // The client itself could not be built (bad URL and the like): configuration, not an outage.
148
+ logger.warn('Omada client could not be created - Omada tools will be disabled', { error: errorMessage(error) });
149
+ omadaClient = undefined;
150
+ }
151
+ upstreams.omadaClient = omadaClient;
152
+ }
153
+ else {
154
+ logger.warn('Omada plugin enabled but required config not set (OMADA_BASE_URL, OMADA_CLIENT_ID, OMADA_CLIENT_SECRET, OMADA_OMADAC_ID)');
155
+ }
156
+ }
157
+ else {
158
+ logger.info('Omada plugin disabled');
159
+ }
160
+ // Local AI
161
+ if (config.aiPluginEnabled) {
162
+ if (config.aiProvider === 'none') {
163
+ logger.info('AI plugin enabled but AI_PROVIDER=none');
164
+ }
165
+ else {
166
+ const aiClient = factories.ai(config);
167
+ upstreams.aiClient = aiClient;
168
+ const check = async () => {
169
+ if (!(await aiClient.checkHealth()))
170
+ throw new Error('health check failed');
171
+ logger.info('Connected to AI provider', { provider: aiClient.getProviderName(), url: config.aiUrl, model: config.aiModel });
172
+ };
173
+ try {
174
+ await check();
175
+ }
176
+ catch (error) {
177
+ logger.warn('AI provider not reachable; the AI tool stays registered and works once it answers. Retrying in the background', {
178
+ provider: config.aiProvider,
179
+ url: config.aiUrl,
180
+ error: errorMessage(error),
181
+ });
182
+ probes.push(retryInBackground('AI provider', check, backoff));
183
+ }
184
+ }
185
+ }
186
+ else {
187
+ logger.info('AI plugin disabled');
188
+ }
189
+ return upstreams;
190
+ }
191
+ function errorMessage(error) {
192
+ return error instanceof Error ? error.message : String(error);
193
+ }
194
+ //# sourceMappingURL=upstreams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upstreams.js","sourceRoot":"","sources":["../src/upstreams.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /** Small IPv4 helpers shared by the Omada access control and the vendor-neutral net_* tools. */
2
+ /** "192.0.2.10" -> 3221225994. Throws on anything that is not a dotted IPv4 address. */
3
+ export declare function ipToNumber(ip: string): number;
4
+ /** "192.0.2.1/24" -> { ip, prefix, network, broadcast } (numbers). Throws on bad input. */
5
+ export declare function parseCidr(value: string): {
6
+ ip: string;
7
+ prefix: number;
8
+ network: number;
9
+ broadcast: number;
10
+ };
11
+ /** Whether an address lies inside a CIDR ("192.0.2.0/24" or a gateway form "192.0.2.1/24"). */
12
+ export declare function inSubnet(address: number, cidr: string): boolean;
13
+ /** Whether two CIDRs share any address. */
14
+ export declare function subnetsOverlap(a: string, b: string): boolean;
@@ -0,0 +1,37 @@
1
+ /** Small IPv4 helpers shared by the Omada access control and the vendor-neutral net_* tools. */
2
+ /** "192.0.2.10" -> 3221225994. Throws on anything that is not a dotted IPv4 address. */
3
+ export function ipToNumber(ip) {
4
+ const parts = ip.trim().split('.');
5
+ if (parts.length !== 4 || parts.some((p) => !/^\d{1,3}$/.test(p) || Number(p) > 255)) {
6
+ throw new Error(`'${ip}' is not an IPv4 address`);
7
+ }
8
+ return parts.reduce((n, p) => n * 256 + Number(p), 0);
9
+ }
10
+ /** "192.0.2.1/24" -> { ip, prefix, network, broadcast } (numbers). Throws on bad input. */
11
+ export function parseCidr(value) {
12
+ const [ip, bits] = value.trim().split('/');
13
+ const address = ipToNumber(ip);
14
+ const prefix = bits === undefined ? 32 : Number(bits);
15
+ if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32)
16
+ throw new Error(`'${value}': the prefix must be 0 to 32`);
17
+ const size = 2 ** (32 - prefix);
18
+ const network = Math.floor(address / size) * size;
19
+ return { ip, prefix, network, broadcast: network + size - 1 };
20
+ }
21
+ /** Whether an address lies inside a CIDR ("192.0.2.0/24" or a gateway form "192.0.2.1/24"). */
22
+ export function inSubnet(address, cidr) {
23
+ try {
24
+ const { network, broadcast } = parseCidr(cidr);
25
+ return address >= network && address <= broadcast;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ /** Whether two CIDRs share any address. */
32
+ export function subnetsOverlap(a, b) {
33
+ const x = parseCidr(a);
34
+ const y = parseCidr(b);
35
+ return x.network <= y.broadcast && y.network <= x.broadcast;
36
+ }
37
+ //# sourceMappingURL=ipv4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipv4.js","sourceRoot":"","sources":["../../src/utils/ipv4.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAEhG,wFAAwF;AACxF,MAAM,UAAU,UAAU,CAAC,EAAU;IACjC,MAAM,KAAK,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,0BAA0B,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,SAAS,CAAC,KAAa;IACnC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,+BAA+B,CAAC,CAAC;IACtH,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAClD,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,IAAY;IAClD,IAAI,CAAC;QACD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC/C,OAAO,OAAO,IAAI,OAAO,IAAI,OAAO,IAAI,SAAS,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,cAAc,CAAC,CAAS,EAAE,CAAS;IAC/C,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACvB,OAAO,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,SAAS,CAAC;AAChE,CAAC"}
package/dist/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Package version - hardcoded at build time for reliability
3
3
  * Keep in sync with package.json version
4
4
  */
5
- export declare const VERSION = "2.4.4";
5
+ export declare const VERSION = "2.4.6";
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  * Package version - hardcoded at build time for reliability
3
3
  * Keep in sync with package.json version
4
4
  */
5
- export const VERSION = '2.4.4';
5
+ export const VERSION = '2.4.6';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffeerunhobby/mcp-ha-connect",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
4
4
  "description": "Smart home MCP server for Home Assistant plus Local AI integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",