@coffeerunhobby/mcp-ha-connect 2.4.5 → 2.4.7
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.
- package/README.md +12 -5
- package/dist/index.js +4 -96
- package/dist/index.js.map +1 -1
- package/dist/net/adapters/omada.d.ts +60 -6
- package/dist/net/adapters/omada.js +507 -34
- package/dist/net/adapters/omada.js.map +1 -1
- package/dist/net/adapters/omadaRules.d.ts +141 -0
- package/dist/net/adapters/omadaRules.js +595 -0
- package/dist/net/adapters/omadaRules.js.map +1 -0
- package/dist/net/model.d.ts +2 -0
- package/dist/net/model.js.map +1 -1
- package/dist/omadaClient/accessControl.d.ts +7 -0
- package/dist/omadaClient/accessControl.js +43 -1
- package/dist/omadaClient/accessControl.js.map +1 -1
- package/dist/omadaClient/index.d.ts +2 -0
- package/dist/omadaClient/index.js +4 -0
- package/dist/omadaClient/index.js.map +1 -1
- package/dist/omadaClient/networkConfig.d.ts +9 -0
- package/dist/omadaClient/networkConfig.js +28 -0
- package/dist/omadaClient/networkConfig.js.map +1 -1
- package/dist/omadaClient/request.d.ts +4 -0
- package/dist/omadaClient/request.js +8 -0
- package/dist/omadaClient/request.js.map +1 -1
- package/dist/tools/net/index.d.ts +113 -1
- package/dist/tools/net/index.js +110 -3
- package/dist/tools/net/index.js.map +1 -1
- package/dist/tools/omada/accessControl.js +1 -0
- package/dist/tools/omada/accessControl.js.map +1 -1
- package/dist/upstreams.d.ts +47 -0
- package/dist/upstreams.js +194 -0
- package/dist/upstreams.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ high-value questions an LLM can actually answer about your network:
|
|
|
77
77
|
> above through two tools — `omada_browse` + `omada_read` — keeping the tool-schema budget
|
|
78
78
|
> small for low-context models. See the Omada tool tables below for the full eager surface.
|
|
79
79
|
|
|
80
|
-
## Available Tools (
|
|
80
|
+
## Available Tools (75 Total)
|
|
81
81
|
|
|
82
82
|
### Home Assistant Tools (38)
|
|
83
83
|
|
|
@@ -251,7 +251,7 @@ Lets the controller enforce a schedule itself (e.g. a curfew) instead of a daily
|
|
|
251
251
|
| `omada_getSiteNtpStatus` | Get the site's NTP server configuration and status |
|
|
252
252
|
| `omada_setSiteNtpServers` | Replace the site's NTP server list (needs `CONFIGURE`; `dryRun` previews the change) |
|
|
253
253
|
|
|
254
|
-
### Vendor-neutral Network Tools (
|
|
254
|
+
### Vendor-neutral Network Tools (15)
|
|
255
255
|
|
|
256
256
|
The `net_` tools describe networks the same way for every controller vendor: one shared model, with an adapter per
|
|
257
257
|
vendor (TP-Link Omada today; UniFi and MikroTik are planned). Each adapter says per feature whether it is native,
|
|
@@ -268,9 +268,16 @@ one is set in the controller's web UI. Reads need `QUERY`, changes `CONFIGURE`.
|
|
|
268
268
|
| `net_createNetwork` / `net_updateNetwork` / `net_deleteNetwork` | Create or change a VLAN with its gateway and DHCP range. The network the controller is on is protected; delete is refused while a Wi-Fi network, port profile, reservation or rule uses it |
|
|
269
269
|
| `net_listWifi` | Wi-Fi networks: network, bands, security, isolation, MAC filter |
|
|
270
270
|
| `net_createWifi` / `net_updateWifi` / `net_deleteWifi` | Create or change an SSID on a given network (bands, WPA2/WPA3, PMF, isolation, hidden, 802.11r, MAC filter), or reset its password to a random one |
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
271
|
+
| `net_listRules` | Traffic rules in evaluation order (the first match decides); rules the model can't express are shown read-only (`foreign`, with the reason) |
|
|
272
|
+
| `net_createRule` / `net_updateRule` / `net_deleteRule` / `net_moveRule` | Allow or block traffic from networks, a group or addresses to networks, a group, addresses (optionally on ports) or the internet, at a chosen position. **Stateful:** a block stops new connections, replies to connections started from the other side still pass ("block IoT → Home" lets Home still reach IoT). Addresses given in a rule become a group the rule owns and removes with it. A change after which the controller (and this server) could no longer reach the internet, in its end state or on the way there, is refused |
|
|
273
|
+
| `net_listGroups` | Address groups (usable in rules) and MAC groups (Wi-Fi allow/deny lists) |
|
|
274
|
+
|
|
275
|
+
On Omada, a network isolation flag (use a block rule instead), a "guest" network type, switching IPv6 on, and WPA2/WPA3
|
|
276
|
+
mixed mode are not available through the API and are refused. Rules are gateway ACLs (the gateway routes between the
|
|
277
|
+
VLANs): LAN to LAN between your networks, LAN to WAN towards the internet. Refused with the reason: the gateway itself as
|
|
278
|
+
a destination (no API option for its own addresses), one device by MAC (give its address), a Wi-Fi network as the
|
|
279
|
+
source, rules that also match replies, schedules (the `omada_` time-range tools do those) and port ranges. Switch
|
|
280
|
+
ports and mDNS arrive in 2.4.8.
|
|
274
281
|
|
|
275
282
|
### AI Tools (1)
|
|
276
283
|
|
package/dist/index.js
CHANGED
|
@@ -5,12 +5,9 @@
|
|
|
5
5
|
* Supports transport modes: stdio, HTTP (Streamable HTTP)
|
|
6
6
|
*/
|
|
7
7
|
import { loadConfig } from './config.js';
|
|
8
|
-
import { HaClient } from './haClient/index.js';
|
|
9
|
-
import { LocalAIClient } from './localAI/index.js';
|
|
10
|
-
import { OmadaClient } from './omadaClient/index.js';
|
|
11
|
-
import { checkConfiguredSite } from './omadaClient/site.js';
|
|
12
8
|
import { startHttpServer } from './server/http.js';
|
|
13
9
|
import { startStdioServer } from './server/stdio.js';
|
|
10
|
+
import { connectUpstreams } from './upstreams.js';
|
|
14
11
|
import { initLogger, logger } from './utils/logger.js';
|
|
15
12
|
import { VERSION } from './version.js';
|
|
16
13
|
async function main() {
|
|
@@ -28,98 +25,9 @@ async function main() {
|
|
|
28
25
|
omadaPluginEnabled: config.omadaPluginEnabled,
|
|
29
26
|
useHttp: config.useHttp,
|
|
30
27
|
});
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (config.baseUrl && config.token) {
|
|
35
|
-
haClient = new HaClient({
|
|
36
|
-
baseUrl: config.baseUrl,
|
|
37
|
-
token: config.token,
|
|
38
|
-
timeout: config.timeout,
|
|
39
|
-
strictSsl: config.strictSsl,
|
|
40
|
-
});
|
|
41
|
-
// Test Home Assistant connection
|
|
42
|
-
const apiCheck = await haClient.checkApi();
|
|
43
|
-
logger.info('Connected to Home Assistant', { message: apiCheck.message });
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
logger.warn('HA plugin enabled but HA_URL or HA_TOKEN not set');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
logger.info('Home Assistant plugin disabled');
|
|
51
|
-
}
|
|
52
|
-
// Create Omada client (if plugin enabled and configured)
|
|
53
|
-
let omadaClient;
|
|
54
|
-
if (config.omadaPluginEnabled) {
|
|
55
|
-
if (config.omadaBaseUrl && config.omadaClientId && config.omadaClientSecret && config.omadacId) {
|
|
56
|
-
try {
|
|
57
|
-
omadaClient = new OmadaClient({
|
|
58
|
-
baseUrl: config.omadaBaseUrl,
|
|
59
|
-
clientId: config.omadaClientId,
|
|
60
|
-
clientSecret: config.omadaClientSecret,
|
|
61
|
-
omadacId: config.omadacId,
|
|
62
|
-
siteId: config.siteId,
|
|
63
|
-
strictSsl: config.omadaStrictSsl,
|
|
64
|
-
requestTimeout: config.requestTimeout,
|
|
65
|
-
});
|
|
66
|
-
// Test Omada connection by listing sites
|
|
67
|
-
const sites = await omadaClient.listSites();
|
|
68
|
-
// Omada is the source of truth: a configured OMADA_SITE_ID that isn't
|
|
69
|
-
// on the controller is a deployment bug (drifted site id), not a
|
|
70
|
-
// transient outage. Fail the Omada plugin loudly — HA and AI keep
|
|
71
|
-
// working — instead of silently scoping every read to a dead site.
|
|
72
|
-
const siteError = checkConfiguredSite(config.siteId, sites);
|
|
73
|
-
if (siteError) {
|
|
74
|
-
logger.error(`Omada plugin disabled - invalid OMADA_SITE_ID. ${siteError}`);
|
|
75
|
-
omadaClient = undefined;
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
logger.info('Connected to Omada controller', { siteCount: sites.length });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
logger.warn('Omada controller not available - Omada tools will be disabled', {
|
|
83
|
-
error: error instanceof Error ? error.message : String(error),
|
|
84
|
-
});
|
|
85
|
-
omadaClient = undefined;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
logger.warn('Omada plugin enabled but required config not set (OMADA_BASE_URL, OMADA_CLIENT_ID, OMADA_CLIENT_SECRET, OMADA_OMADAC_ID)');
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
logger.info('Omada plugin disabled');
|
|
94
|
-
}
|
|
95
|
-
// Create Local AI client (if plugin enabled)
|
|
96
|
-
let aiClient;
|
|
97
|
-
if (config.aiPluginEnabled) {
|
|
98
|
-
if (config.aiProvider === 'none') {
|
|
99
|
-
logger.info('AI plugin enabled but AI_PROVIDER=none');
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
aiClient = new LocalAIClient(config);
|
|
103
|
-
const aiHealthy = await aiClient.checkHealth();
|
|
104
|
-
if (aiHealthy) {
|
|
105
|
-
logger.info('Connected to AI provider', {
|
|
106
|
-
provider: aiClient.getProviderName(),
|
|
107
|
-
url: config.aiUrl,
|
|
108
|
-
model: config.aiModel,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
logger.warn('AI provider not available - AI analysis tool will be disabled', {
|
|
113
|
-
provider: config.aiProvider,
|
|
114
|
-
url: config.aiUrl,
|
|
115
|
-
});
|
|
116
|
-
aiClient = undefined;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
logger.info('AI plugin disabled');
|
|
122
|
-
}
|
|
28
|
+
// Connect the upstreams. One that doesn't answer yet doesn't stop the server:
|
|
29
|
+
// it is retried in the background (see upstreams.ts).
|
|
30
|
+
const { haClient, omadaClient, aiClient } = await connectUpstreams(config);
|
|
123
31
|
// Start server in appropriate mode
|
|
124
32
|
if (config.useHttp) {
|
|
125
33
|
await startHttpServer({ haClient, omadaClient, aiClient, config });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Omada adapter for the vendor-neutral network model (src/net/model.ts).
|
|
3
3
|
*
|
|
4
|
-
* 2.4.5 covers networks (LAN networks / VLANs) and Wi-Fi (SSIDs).
|
|
5
|
-
* ports and mDNS arrive with 2.4.
|
|
6
|
-
* NetError UNSUPPORTED.
|
|
4
|
+
* 2.4.5 covers networks (LAN networks / VLANs) and Wi-Fi (SSIDs); 2.4.7 rules (gateway
|
|
5
|
+
* ACLs, see omadaRules.ts) and listing groups. Switch ports and mDNS arrive with 2.4.8;
|
|
6
|
+
* until then those calls fail with NetError UNSUPPORTED.
|
|
7
7
|
*
|
|
8
8
|
* Omada specifics handled here:
|
|
9
9
|
* - networks have no "guest" purpose and no isolation flag in the API (guest is per
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - Omada lists every SSID again under a pseudo WLAN group "gateway" without detail.
|
|
15
15
|
*/
|
|
16
16
|
import type { OmadaClient } from '../../omadaClient/index.js';
|
|
17
|
-
import { type ApplyResult, type Capabilities, type Change, type Client, type Network, type NetworkController, type Plan, type ProtectedResources, type Reservation, type Wifi } from '../model.js';
|
|
17
|
+
import { type ApplyResult, type Capabilities, type Change, type Client, type Group, type Network, type NetworkController, type Plan, type ProtectedResources, type Reservation, type Rule, type Wifi } from '../model.js';
|
|
18
18
|
export interface OmadaAdapterOptions {
|
|
19
19
|
siteId?: string;
|
|
20
20
|
/** The controller's host name or address (host of OMADA_BASE_URL): its network is protected. */
|
|
@@ -49,8 +49,8 @@ export declare class OmadaNetworkController implements NetworkController {
|
|
|
49
49
|
listReservations(): Promise<Reservation[]>;
|
|
50
50
|
listPortProfiles(): Promise<never>;
|
|
51
51
|
listPorts(): Promise<never>;
|
|
52
|
-
listRules(): Promise<
|
|
53
|
-
listGroups(): Promise<
|
|
52
|
+
listRules(): Promise<Rule[]>;
|
|
53
|
+
listGroups(kind?: Group['kind']): Promise<Group[]>;
|
|
54
54
|
getMdnsRepeater(): Promise<never>;
|
|
55
55
|
plan(change: Change): Promise<Plan>;
|
|
56
56
|
apply(plan: Plan, options?: {
|
|
@@ -58,6 +58,60 @@ export declare class OmadaNetworkController implements NetworkController {
|
|
|
58
58
|
}): Promise<ApplyResult>;
|
|
59
59
|
private planWith;
|
|
60
60
|
private build;
|
|
61
|
+
private ruleWorld;
|
|
62
|
+
/** The rule as the model sees it, with its raw ACL; a rule the model can't express is refused. */
|
|
63
|
+
private editableRule;
|
|
64
|
+
private checkPosition;
|
|
65
|
+
/**
|
|
66
|
+
* Refuses a change if any rule list it passes through (the end state, and the staged
|
|
67
|
+
* one where the rule is switched off) could expose some of the controller's (and this
|
|
68
|
+
* server's) internet traffic to a block that doesn't reach it today (controllerRisk).
|
|
69
|
+
* Waived only when the controller is certainly cut off already.
|
|
70
|
+
*/
|
|
71
|
+
private guardController;
|
|
72
|
+
private buildRuleCreate;
|
|
73
|
+
private buildRuleUpdate;
|
|
74
|
+
private buildRuleDelete;
|
|
75
|
+
private buildRuleMove;
|
|
76
|
+
/**
|
|
77
|
+
* Existing ids for an endpoint, or the id of the group created for it. The group is
|
|
78
|
+
* tracked by its (unique) name before it is created, so a create whose answer or
|
|
79
|
+
* read-back got lost can still be found and undone.
|
|
80
|
+
*/
|
|
81
|
+
private materialize;
|
|
82
|
+
/**
|
|
83
|
+
* Undoes a create that failed part-way and returns the error to throw. A create that
|
|
84
|
+
* was sent may have happened even if it reported an error: the rule is looked up by
|
|
85
|
+
* its description and removed before its groups. When that can't be confirmed, the
|
|
86
|
+
* groups stay (a live rule must never point at deleted groups) and the error says so.
|
|
87
|
+
*/
|
|
88
|
+
private undoCreate;
|
|
89
|
+
/**
|
|
90
|
+
* Undoes an update that failed part-way and returns the error to throw: puts the rule
|
|
91
|
+
* back as it was (if the change may have been written) and removes the groups made
|
|
92
|
+
* for it, unless the rule might still point at them.
|
|
93
|
+
*/
|
|
94
|
+
private undoUpdate;
|
|
95
|
+
/** Puts the rule order back after a move that failed (it may have happened anyway); returns the error to throw. */
|
|
96
|
+
private restoreOrder;
|
|
97
|
+
/** Deletes groups made during a failed change (found by name when the id is unknown); returns what it couldn't remove. */
|
|
98
|
+
private dropGroups;
|
|
99
|
+
/**
|
|
100
|
+
* Deletes every group a net_ rule owned that no ACL (gateway, switch or access point)
|
|
101
|
+
* references any more. Run after each successful rule change, so a cleanup that failed
|
|
102
|
+
* once is finished by the next change; a failure here is a warning, not an error (the
|
|
103
|
+
* change itself is done). Writes are serialized per site, so no rule is half-created
|
|
104
|
+
* meanwhile.
|
|
105
|
+
*/
|
|
106
|
+
private sweepOwnedGroups;
|
|
107
|
+
private sortedAcls;
|
|
108
|
+
/** Moves one rule to `position` (1 = first); the others keep their order. */
|
|
109
|
+
private reorder;
|
|
110
|
+
/**
|
|
111
|
+
* The result of a change that is done: the rule as it reads back, plus warnings. A
|
|
112
|
+
* failed read-back is a warning, never an error: the change is already applied.
|
|
113
|
+
*/
|
|
114
|
+
private done;
|
|
61
115
|
private buildNetworkCreate;
|
|
62
116
|
private buildNetworkUpdate;
|
|
63
117
|
private buildNetworkDelete;
|