@coffeerunhobby/mcp-ha-connect 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -1
- package/README.md +11 -2
- package/dist/omadaClient/client.d.ts +6 -1
- package/dist/omadaClient/client.js +12 -1
- package/dist/omadaClient/client.js.map +1 -1
- package/dist/omadaClient/diagnose.d.ts +68 -0
- package/dist/omadaClient/diagnose.js +164 -0
- package/dist/omadaClient/diagnose.js.map +1 -0
- package/dist/omadaClient/index.d.ts +6 -0
- package/dist/omadaClient/index.js +15 -0
- package/dist/omadaClient/index.js.map +1 -1
- package/dist/omadaClient/macGroups.d.ts +58 -0
- package/dist/omadaClient/macGroups.js +124 -0
- package/dist/omadaClient/macGroups.js.map +1 -0
- package/dist/tools/infra/actions.d.ts +1 -1
- package/dist/tools/infra/actions.js +1 -1
- package/dist/tools/omada/index.js +6 -0
- package/dist/tools/omada/index.js.map +1 -1
- package/dist/tools/omada/macGroups.d.ts +19 -0
- package/dist/tools/omada/macGroups.js +77 -0
- package/dist/tools/omada/macGroups.js.map +1 -0
- package/dist/tools/omada/namespace.js +8 -0
- package/dist/tools/omada/namespace.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -1
- package/.codex/review-rules.md +0 -16
- package/.dockerignore +0 -8
- package/.gitattributes +0 -30
- package/.github/workflows/ci.yml +0 -193
- package/AGENTS.md +0 -389
- package/Dockerfile +0 -50
- package/docker-compose.yml +0 -23
- package/docs/CHANGELOG.md +0 -341
- package/docs/COVERAGE.md +0 -219
- package/docs/QUICK_START.md +0 -240
- package/eslint.config.js +0 -33
- package/n8n/ai-agent-safety-monitor.json +0 -355
- package/openapi.json +0 -1259
- package/scripts/check-version-sync.mjs +0 -69
- package/scripts/generate-jwt.ts +0 -121
- package/src/config.ts +0 -440
- package/src/haClient/automations.ts +0 -261
- package/src/haClient/calendars.ts +0 -94
- package/src/haClient/config.ts +0 -52
- package/src/haClient/devices.ts +0 -653
- package/src/haClient/entities.ts +0 -128
- package/src/haClient/events.ts +0 -689
- package/src/haClient/history.ts +0 -73
- package/src/haClient/index.ts +0 -336
- package/src/haClient/request.ts +0 -142
- package/src/haClient/services.ts +0 -45
- package/src/haClient/states.ts +0 -60
- package/src/haClient/updates.ts +0 -58
- package/src/haClient/wsCommand.ts +0 -44
- package/src/index.ts +0 -164
- package/src/localAI/index.ts +0 -88
- package/src/localAI/prompts.ts +0 -50
- package/src/localAI/providers/base.ts +0 -120
- package/src/localAI/providers/ollama.ts +0 -78
- package/src/localAI/providers/openai.ts +0 -86
- package/src/localAI/types.ts +0 -67
- package/src/omadaClient/auth.ts +0 -178
- package/src/omadaClient/client.ts +0 -337
- package/src/omadaClient/device.ts +0 -118
- package/src/omadaClient/index.ts +0 -310
- package/src/omadaClient/network.ts +0 -284
- package/src/omadaClient/request.ts +0 -365
- package/src/omadaClient/security.ts +0 -51
- package/src/omadaClient/site.ts +0 -344
- package/src/ownerQuestions/service.ts +0 -536
- package/src/permissions/index.ts +0 -143
- package/src/resources/index.ts +0 -213
- package/src/server/auth.ts +0 -175
- package/src/server/chatFace.ts +0 -789
- package/src/server/chatSlice.ts +0 -57
- package/src/server/common.ts +0 -170
- package/src/server/eventSubscription.ts +0 -343
- package/src/server/http.ts +0 -626
- package/src/server/instructions.ts +0 -97
- package/src/server/rateLimiter.ts +0 -227
- package/src/server/stdio.ts +0 -41
- package/src/server/stream.ts +0 -205
- package/src/tools/ai/analyzeSensors.ts +0 -33
- package/src/tools/ai/index.ts +0 -20
- package/src/tools/common.ts +0 -442
- package/src/tools/homeassistant/askOwner.ts +0 -133
- package/src/tools/homeassistant/automationControls.ts +0 -66
- package/src/tools/homeassistant/calendar.ts +0 -97
- package/src/tools/homeassistant/callService.ts +0 -25
- package/src/tools/homeassistant/controlClimate.ts +0 -68
- package/src/tools/homeassistant/controlCover.ts +0 -48
- package/src/tools/homeassistant/controlFan.ts +0 -44
- package/src/tools/homeassistant/controlLight.ts +0 -38
- package/src/tools/homeassistant/controlMediaPlayer.ts +0 -50
- package/src/tools/homeassistant/createAutomation.ts +0 -35
- package/src/tools/homeassistant/deleteAutomation.ts +0 -24
- package/src/tools/homeassistant/entityAction.ts +0 -35
- package/src/tools/homeassistant/entityGuard.ts +0 -28
- package/src/tools/homeassistant/getAllSensors.ts +0 -46
- package/src/tools/homeassistant/getAutomationTrace.ts +0 -30
- package/src/tools/homeassistant/getDomainSummary.ts +0 -29
- package/src/tools/homeassistant/getEntitiesByDomain.ts +0 -56
- package/src/tools/homeassistant/getHistory.ts +0 -24
- package/src/tools/homeassistant/getState.ts +0 -24
- package/src/tools/homeassistant/getStates.ts +0 -46
- package/src/tools/homeassistant/getVersion.ts +0 -21
- package/src/tools/homeassistant/index.ts +0 -121
- package/src/tools/homeassistant/listAutomations.ts +0 -35
- package/src/tools/homeassistant/listEntities.ts +0 -44
- package/src/tools/homeassistant/listPersons.ts +0 -46
- package/src/tools/homeassistant/sceneAndScript.ts +0 -53
- package/src/tools/homeassistant/searchEntities.ts +0 -58
- package/src/tools/homeassistant/sendNotification.ts +0 -179
- package/src/tools/homeassistant/systemTools.ts +0 -52
- package/src/tools/homeassistant/triggerAutomation.ts +0 -24
- package/src/tools/index.ts +0 -78
- package/src/tools/infra/actions.ts +0 -95
- package/src/tools/infra/index.ts +0 -138
- package/src/tools/omada/blockClient.ts +0 -28
- package/src/tools/omada/cyclePoePort.ts +0 -32
- package/src/tools/omada/deleteClient.ts +0 -27
- package/src/tools/omada/disableClientRateLimit.ts +0 -23
- package/src/tools/omada/getClient.ts +0 -14
- package/src/tools/omada/getDevice.ts +0 -14
- package/src/tools/omada/getFirewallSetting.ts +0 -14
- package/src/tools/omada/getInternetInfo.ts +0 -14
- package/src/tools/omada/getLanNetworkList.ts +0 -14
- package/src/tools/omada/getLanProfileList.ts +0 -14
- package/src/tools/omada/getPortForwardingStatus.ts +0 -26
- package/src/tools/omada/getRateLimitProfiles.ts +0 -14
- package/src/tools/omada/getSiteNtpStatus.ts +0 -14
- package/src/tools/omada/getSsidDetail.ts +0 -22
- package/src/tools/omada/getSsidList.ts +0 -21
- package/src/tools/omada/getSwitchStackDetail.ts +0 -14
- package/src/tools/omada/getThreatList.ts +0 -42
- package/src/tools/omada/getWlanGroupList.ts +0 -14
- package/src/tools/omada/graph.ts +0 -187
- package/src/tools/omada/index.ts +0 -153
- package/src/tools/omada/listBlockedClients.ts +0 -17
- package/src/tools/omada/listClients.ts +0 -14
- package/src/tools/omada/listClientsActivity.ts +0 -25
- package/src/tools/omada/listClientsPastConnections.ts +0 -44
- package/src/tools/omada/listDevices.ts +0 -14
- package/src/tools/omada/listDevicesStats.ts +0 -41
- package/src/tools/omada/listMostActiveClients.ts +0 -14
- package/src/tools/omada/listSites.ts +0 -18
- package/src/tools/omada/namespace.ts +0 -1164
- package/src/tools/omada/searchDevices.ts +0 -20
- package/src/tools/omada/setClientRateLimit.ts +0 -26
- package/src/tools/omada/setClientRateLimitProfile.ts +0 -25
- package/src/tools/omada/setSiteNtpServers.ts +0 -36
- package/src/tools/omada/setSsidEnabled.ts +0 -38
- package/src/tools/omada/unblockClient.ts +0 -26
- package/src/tools/types.ts +0 -11
- package/src/tools/utils/pagination-schema.ts +0 -27
- package/src/types/ai/index.ts +0 -11
- package/src/types/ai/types.ts +0 -67
- package/src/types/homeassistant/automations.ts +0 -43
- package/src/types/homeassistant/calendars.ts +0 -26
- package/src/types/homeassistant/config.ts +0 -14
- package/src/types/homeassistant/entities.ts +0 -14
- package/src/types/homeassistant/errors.ts +0 -33
- package/src/types/homeassistant/haConfig.ts +0 -7
- package/src/types/homeassistant/history.ts +0 -13
- package/src/types/homeassistant/index.ts +0 -46
- package/src/types/homeassistant/request.ts +0 -6
- package/src/types/homeassistant/serviceCall.ts +0 -18
- package/src/types/homeassistant/states.ts +0 -58
- package/src/types/homeassistant/updates.ts +0 -28
- package/src/types/index.ts +0 -12
- package/src/types/omada/activeClientInfo.ts +0 -17
- package/src/types/omada/clientActivity.ts +0 -32
- package/src/types/omada/clientBlockStatus.ts +0 -11
- package/src/types/omada/clientPastConnection.ts +0 -82
- package/src/types/omada/index.ts +0 -14
- package/src/types/omada/omadaApiResponse.ts +0 -5
- package/src/types/omada/omadaClientInfo.ts +0 -11
- package/src/types/omada/omadaDeviceInfo.ts +0 -11
- package/src/types/omada/omadaDeviceStats.ts +0 -31
- package/src/types/omada/omadaSiteSummary.ts +0 -5
- package/src/types/omada/oswStackDetail.ts +0 -11
- package/src/types/omada/paginatedResult.ts +0 -6
- package/src/types/omada/rateLimitProfile.ts +0 -58
- package/src/types/omada/threatInfo.ts +0 -38
- package/src/types/omada/tokenResult.ts +0 -6
- package/src/types.ts +0 -2
- package/src/utils/config-validations.ts +0 -68
- package/src/utils/jwt.ts +0 -112
- package/src/utils/logger.ts +0 -121
- package/src/utils/plainText.ts +0 -48
- package/src/utils/redact.ts +0 -59
- package/src/utils/sanitizeError.ts +0 -18
- package/src/utils/tlsDispatcher.ts +0 -69
- package/src/version.ts +0 -5
- package/tests/bdd/automation-creation.spec.ts +0 -214
- package/tests/bdd/automation-management.spec.ts +0 -267
- package/tests/bdd/calendar.spec.ts +0 -190
- package/tests/bdd/configuration.spec.ts +0 -283
- package/tests/bdd/device-control.spec.ts +0 -344
- package/tests/bdd/entity-listing.spec.ts +0 -208
- package/tests/bdd/entity-search.spec.ts +0 -183
- package/tests/bdd/history-retrieval.spec.ts +0 -195
- package/tests/bdd/notifications.spec.ts +0 -238
- package/tests/bdd/service-calls.spec.ts +0 -144
- package/tests/bdd/state-retrieval.spec.ts +0 -204
- package/tests/bench/hotpaths.bench.ts +0 -68
- package/tests/config.test.ts +0 -355
- package/tests/features/automation-creation.feature +0 -36
- package/tests/features/automation-management.feature +0 -54
- package/tests/features/calendar.feature +0 -37
- package/tests/features/configuration.feature +0 -70
- package/tests/features/device-control.feature +0 -100
- package/tests/features/entity-listing.feature +0 -51
- package/tests/features/entity-search.feature +0 -44
- package/tests/features/history-retrieval.feature +0 -38
- package/tests/features/notifications.feature +0 -37
- package/tests/features/service-calls.feature +0 -35
- package/tests/features/state-retrieval.feature +0 -37
- package/tests/fixtures/openapi-default-slice.json +0 -290
- package/tests/haClient/automationTrace.test.ts +0 -59
- package/tests/haClient/automations.test.ts +0 -149
- package/tests/haClient/config.test.ts +0 -117
- package/tests/haClient/entities.test.ts +0 -188
- package/tests/haClient/events.test.ts +0 -455
- package/tests/haClient/eventsTls.test.ts +0 -52
- package/tests/haClient/history.test.ts +0 -228
- package/tests/haClient/index.test.ts +0 -243
- package/tests/haClient/request.test.ts +0 -217
- package/tests/haClient/services.test.ts +0 -120
- package/tests/haClient/states.test.ts +0 -119
- package/tests/haClient/updates.test.ts +0 -193
- package/tests/index.test.ts +0 -160
- package/tests/integration/homeassistant.test.ts +0 -144
- package/tests/integration/omada.test.ts +0 -167
- package/tests/integration/server.test.ts +0 -151
- package/tests/localAI/index.test.ts +0 -177
- package/tests/localAI/ollama.test.ts +0 -187
- package/tests/localAI/openai.test.ts +0 -257
- package/tests/omadaClient/actions.test.ts +0 -161
- package/tests/omadaClient/clientBlocks.test.ts +0 -170
- package/tests/omadaClient/ntp.test.ts +0 -381
- package/tests/omadaClient/site.test.ts +0 -37
- package/tests/ownerQuestions/service.test.ts +0 -499
- package/tests/ownerQuestions/tools.test.ts +0 -186
- package/tests/permissions/index.test.ts +0 -217
- package/tests/resources/index.test.ts +0 -288
- package/tests/scripts/generateJwt.test.ts +0 -44
- package/tests/security/accessControl.test.ts +0 -223
- package/tests/security/authentication.test.ts +0 -117
- package/tests/security/configuration.test.ts +0 -170
- package/tests/security/cryptoTransport.test.ts +0 -172
- package/tests/security/dependencies.test.ts +0 -152
- package/tests/security/injection.test.ts +0 -117
- package/tests/security/insecureDesign.test.ts +0 -121
- package/tests/security/logging.test.ts +0 -131
- package/tests/security/resourceConsumption.test.ts +0 -142
- package/tests/server/auth.test.ts +0 -249
- package/tests/server/chatFace.test.ts +0 -320
- package/tests/server/common.test.ts +0 -84
- package/tests/server/http.test.ts +0 -561
- package/tests/server/instructions.test.ts +0 -152
- package/tests/server/memoizeJsonSchema.test.ts +0 -71
- package/tests/server/stdio.test.ts +0 -99
- package/tests/server/stream.test.ts +0 -285
- package/tests/server/toolVisibility.http.test.ts +0 -111
- package/tests/server/toolVisibility.test.ts +0 -129
- package/tests/tools/ai/analyzeSensors.test.ts +0 -227
- package/tests/tools/common.test.ts +0 -123
- package/tests/tools/homeassistant/handlers.test.ts +0 -872
- package/tests/tools/infra/invokeAction.test.ts +0 -280
- package/tests/tools/omada/graph.test.ts +0 -385
- package/tests/tools/omada/handlers.test.ts +0 -897
- package/tests/tools/omada/namespace.test.ts +0 -483
- package/tests/tools/registry.test.ts +0 -168
- package/tests/utils/config-validations.test.ts +0 -352
- package/tests/utils/jwt.test.ts +0 -115
- package/tests/utils/logger.test.ts +0 -327
- package/tests/utils/redact.test.ts +0 -105
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -38
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* check-version-sync — fails if the version string has drifted across the files
|
|
4
|
-
* that must agree. CLAUDE.md calls this out explicitly: openapi.json's version
|
|
5
|
-
* "drifts easily" on release. This is the durable gate that makes the drift loud
|
|
6
|
-
* instead of silent.
|
|
7
|
-
*
|
|
8
|
-
* Sources of truth that MUST match:
|
|
9
|
-
* - package.json -> .version
|
|
10
|
-
* - src/version.ts -> export const VERSION = '...'
|
|
11
|
-
* - openapi.json -> .info.version
|
|
12
|
-
*
|
|
13
|
-
* Exit 0 when all agree; exit 1 with a diff when they don't. No dependencies,
|
|
14
|
-
* no build step — plain Node ESM so CI can run it with `node`.
|
|
15
|
-
*/
|
|
16
|
-
import { readFileSync } from 'node:fs';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
18
|
-
import { dirname, join } from 'node:path';
|
|
19
|
-
|
|
20
|
-
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
21
|
-
|
|
22
|
-
/** Read a file, or throw a clear error naming the missing path. */
|
|
23
|
-
function read(rel) {
|
|
24
|
-
try {
|
|
25
|
-
return readFileSync(join(root, rel), 'utf8');
|
|
26
|
-
} catch (e) {
|
|
27
|
-
throw new Error(`Cannot read ${rel}: ${e.message}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const sources = [];
|
|
32
|
-
|
|
33
|
-
// package.json -> .version
|
|
34
|
-
{
|
|
35
|
-
const pkg = JSON.parse(read('package.json'));
|
|
36
|
-
sources.push({ file: 'package.json', version: pkg.version });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// src/version.ts -> export const VERSION = 'X.Y.Z'
|
|
40
|
-
{
|
|
41
|
-
const txt = read('src/version.ts');
|
|
42
|
-
const m = txt.match(/export\s+const\s+VERSION\s*=\s*['"]([^'"]+)['"]/);
|
|
43
|
-
sources.push({ file: 'src/version.ts', version: m ? m[1] : undefined });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// openapi.json -> .info.version
|
|
47
|
-
{
|
|
48
|
-
const spec = JSON.parse(read('openapi.json'));
|
|
49
|
-
sources.push({ file: 'openapi.json', version: spec?.info?.version });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const missing = sources.filter((s) => !s.version);
|
|
53
|
-
if (missing.length > 0) {
|
|
54
|
-
console.error('✖ version-sync: could not extract a version from:');
|
|
55
|
-
for (const m of missing) console.error(` - ${m.file}`);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const unique = [...new Set(sources.map((s) => s.version))];
|
|
60
|
-
if (unique.length > 1) {
|
|
61
|
-
console.error('✖ version-sync: version strings disagree across files:');
|
|
62
|
-
for (const s of sources) console.error(` ${s.version.padEnd(12)} ${s.file}`);
|
|
63
|
-
console.error('\n Fix: set all three to the same version before releasing.');
|
|
64
|
-
console.error(' (package.json, src/version.ts, openapi.json — see CLAUDE.md Release Process.)');
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
console.log(`✓ version-sync: all sources agree on ${unique[0]}`);
|
|
69
|
-
process.exit(0);
|
package/scripts/generate-jwt.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
/**
|
|
3
|
-
* Generate JWT tokens for MCP server authentication
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* npx tsx scripts/generate-jwt.ts [options]
|
|
7
|
-
* npm run generate:jwt -- [options]
|
|
8
|
-
*
|
|
9
|
-
* Options:
|
|
10
|
-
* --sub <user> Subject (username), default: 'admin'
|
|
11
|
-
* --exp <duration> Expiration (e.g., '1h', '7d', '30d'), default: '7d'
|
|
12
|
-
* --secret <secret> JWT secret (or use MCP_AUTH_SECRET env var)
|
|
13
|
-
*
|
|
14
|
-
* Examples:
|
|
15
|
-
* npm run generate:jwt
|
|
16
|
-
* npm run generate:jwt -- --sub claude --exp 30d
|
|
17
|
-
* npm run generate:jwt -- --sub readonly --exp 1h --secret mysecret
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { createJwt } from '../src/utils/jwt.js';
|
|
21
|
-
|
|
22
|
-
function parseArgs(): { sub: string; exp: string; secret: string } {
|
|
23
|
-
const args = process.argv.slice(2);
|
|
24
|
-
let sub = 'admin';
|
|
25
|
-
let exp = '7d';
|
|
26
|
-
let secret = process.env.MCP_AUTH_SECRET || '';
|
|
27
|
-
|
|
28
|
-
for (let i = 0; i < args.length; i++) {
|
|
29
|
-
if (args[i] === '--sub' && args[i + 1]) {
|
|
30
|
-
sub = args[++i];
|
|
31
|
-
} else if (args[i] === '--exp' && args[i + 1]) {
|
|
32
|
-
exp = args[++i];
|
|
33
|
-
} else if (args[i] === '--secret' && args[i + 1]) {
|
|
34
|
-
secret = args[++i];
|
|
35
|
-
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
36
|
-
console.log(`
|
|
37
|
-
Generate JWT tokens for MCP server authentication
|
|
38
|
-
|
|
39
|
-
Usage:
|
|
40
|
-
npm run generate:jwt -- [options]
|
|
41
|
-
|
|
42
|
-
Options:
|
|
43
|
-
--sub <user> Subject (username), default: 'admin'
|
|
44
|
-
--exp <duration> Expiration (e.g., '1h', '7d', '30d'), default: '7d'
|
|
45
|
-
--secret <secret> JWT secret (or use MCP_AUTH_SECRET env var)
|
|
46
|
-
|
|
47
|
-
Examples:
|
|
48
|
-
npm run generate:jwt
|
|
49
|
-
npm run generate:jwt -- --sub claude --exp 30d
|
|
50
|
-
npm run generate:jwt -- --sub readonly --exp 1h
|
|
51
|
-
`);
|
|
52
|
-
process.exit(0);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return { sub, exp, secret };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function parseDuration(duration: string): number {
|
|
60
|
-
const match = duration.match(/^(\d+)([smhd])$/);
|
|
61
|
-
if (!match) {
|
|
62
|
-
throw new Error(`Invalid duration: ${duration}. Use format like '1h', '7d', '30d'`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const value = parseInt(match[1], 10);
|
|
66
|
-
const unit = match[2];
|
|
67
|
-
|
|
68
|
-
const now = Math.floor(Date.now() / 1000);
|
|
69
|
-
switch (unit) {
|
|
70
|
-
case 's':
|
|
71
|
-
return now + value;
|
|
72
|
-
case 'm':
|
|
73
|
-
return now + value * 60;
|
|
74
|
-
case 'h':
|
|
75
|
-
return now + value * 3600;
|
|
76
|
-
case 'd':
|
|
77
|
-
return now + value * 86400;
|
|
78
|
-
default:
|
|
79
|
-
throw new Error(`Unknown unit: ${unit}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function main() {
|
|
84
|
-
const { sub, exp, secret } = parseArgs();
|
|
85
|
-
|
|
86
|
-
if (!secret) {
|
|
87
|
-
console.error('Error: No secret provided.');
|
|
88
|
-
console.error('Set MCP_AUTH_SECRET environment variable or use --secret option.');
|
|
89
|
-
console.error('\nExample:');
|
|
90
|
-
console.error(' export MCP_AUTH_SECRET=your-secret-here');
|
|
91
|
-
console.error(' npm run generate:jwt');
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (secret.length < 32) {
|
|
96
|
-
console.error('Warning: Secret should be at least 32 characters for security.');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const iat = Math.floor(Date.now() / 1000);
|
|
100
|
-
const expTime = parseDuration(exp);
|
|
101
|
-
|
|
102
|
-
const payload = {
|
|
103
|
-
sub,
|
|
104
|
-
iat,
|
|
105
|
-
exp: expTime,
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const token = createJwt(payload, secret);
|
|
109
|
-
|
|
110
|
-
// Details go to stderr; stdout carries ONLY the bare token, exactly once, so
|
|
111
|
-
// `TOKEN=$(npm run -s generate:jwt -- ...)` captures one usable token.
|
|
112
|
-
console.error('\n=== JWT Token Generated ===\n');
|
|
113
|
-
console.error('Subject:', sub);
|
|
114
|
-
console.error('Issued at:', new Date(iat * 1000).toISOString());
|
|
115
|
-
console.error('Expires at:', new Date(expTime * 1000).toISOString());
|
|
116
|
-
console.error('Duration:', exp);
|
|
117
|
-
console.error('\nUsage: curl -H "Authorization: Bearer <token>" http://localhost:3000/mcp ...\n');
|
|
118
|
-
console.log(token);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
main();
|
package/src/config.ts
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { isValidBindAddress, isValidOrigin, isLoopbackAddress } from './utils/config-validations.js';
|
|
3
|
-
import { parsePermissionsConfig, type PermissionsConfig } from './permissions/index.js';
|
|
4
|
-
import { parseRestActions, type RestAction } from './tools/infra/actions.js';
|
|
5
|
-
import { parseChatTools, type ChatToolsSlice } from './server/chatSlice.js';
|
|
6
|
-
import { logger } from './utils/logger.js';
|
|
7
|
-
import type { AIProviderType } from './localAI/types.js';
|
|
8
|
-
|
|
9
|
-
const createBooleanStringSchema = (defaultValue: boolean) =>
|
|
10
|
-
z
|
|
11
|
-
.union([z.literal('true'), z.literal('false')])
|
|
12
|
-
.optional()
|
|
13
|
-
.transform((value: 'true' | 'false' | undefined) => {
|
|
14
|
-
if (value === undefined) return defaultValue;
|
|
15
|
-
return value === 'true';
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const numericStringSchema = z
|
|
19
|
-
.string()
|
|
20
|
-
.optional()
|
|
21
|
-
.transform((value: string | undefined) => (value ? Number.parseInt(value, 10) : undefined))
|
|
22
|
-
.pipe(z.number().positive().optional());
|
|
23
|
-
|
|
24
|
-
const envSchema = z
|
|
25
|
-
.object({
|
|
26
|
-
// Plugin Enable Flags (default: false)
|
|
27
|
-
haPluginEnabled: createBooleanStringSchema(false),
|
|
28
|
-
aiPluginEnabled: createBooleanStringSchema(false),
|
|
29
|
-
omadaPluginEnabled: createBooleanStringSchema(false),
|
|
30
|
-
|
|
31
|
-
// Home Assistant Client Configuration (optional if using Omada only)
|
|
32
|
-
haUrl: z.string().url({ message: 'HA_URL must be a valid URL' }).optional(),
|
|
33
|
-
haToken: z.string().min(1).optional(),
|
|
34
|
-
haStrictSsl: createBooleanStringSchema(true),
|
|
35
|
-
haTimeout: numericStringSchema,
|
|
36
|
-
|
|
37
|
-
// Omada Client Configuration (optional)
|
|
38
|
-
omadaBaseUrl: z.string().url({ message: 'OMADA_BASE_URL must be a valid URL' }).optional(),
|
|
39
|
-
omadaClientId: z.string().min(1).optional(),
|
|
40
|
-
omadaClientSecret: z.string().min(1).optional(),
|
|
41
|
-
omadaOmadacId: z.string().min(1).optional(),
|
|
42
|
-
omadaSiteId: z.string().min(1).optional(),
|
|
43
|
-
omadaStrictSsl: createBooleanStringSchema(true),
|
|
44
|
-
omadaTimeout: numericStringSchema,
|
|
45
|
-
|
|
46
|
-
// Pre-registered REST actions for the invokeAction tool (optional; raw JSON,
|
|
47
|
-
// parsed + validated by parseRestActions after the env parse)
|
|
48
|
-
restActions: z.string().optional(),
|
|
49
|
-
|
|
50
|
-
// Chat-face tool slice for the OpenAPI/REST surface (optional; raw
|
|
51
|
-
// `category:rw` list, parsed + validated by parseChatTools after the env parse)
|
|
52
|
-
chatTools: z.string().optional(),
|
|
53
|
-
|
|
54
|
-
// AI Provider Configuration (use 'none' to disable AI features)
|
|
55
|
-
aiProvider: z.enum(['ollama', 'openai', 'none']).optional(),
|
|
56
|
-
aiUrl: z.string().url().optional(),
|
|
57
|
-
aiModel: z.string().optional(),
|
|
58
|
-
aiTimeout: numericStringSchema,
|
|
59
|
-
aiApiKey: z.string().optional(),
|
|
60
|
-
|
|
61
|
-
// MCP Generic Server Configuration
|
|
62
|
-
logLevel: z.enum(['debug', 'info', 'warn', 'error']).optional().default('info'),
|
|
63
|
-
logFormat: z.enum(['plain', 'json', 'gcp-json']).optional().default('plain'),
|
|
64
|
-
useHttp: createBooleanStringSchema(false),
|
|
65
|
-
stateful: createBooleanStringSchema(false),
|
|
66
|
-
// Tool registration strategy. `eager` (default) registers every typed tool;
|
|
67
|
-
// `graph` registers the Omada resource-graph reads (omada_browse/omada_read)
|
|
68
|
-
// plus typed writes, shrinking the tool-schema budget for low-context models.
|
|
69
|
-
toolRegistrationMode: z.enum(['eager', 'graph']).optional().default('eager'),
|
|
70
|
-
|
|
71
|
-
// MCP Server HTTP Configuration
|
|
72
|
-
httpPort: numericStringSchema,
|
|
73
|
-
httpBindAddr: z.string().optional(),
|
|
74
|
-
httpPath: z.string().optional(),
|
|
75
|
-
httpEnableHealthcheck: createBooleanStringSchema(true),
|
|
76
|
-
httpHealthcheckPath: z.string().optional(),
|
|
77
|
-
httpAllowCors: createBooleanStringSchema(true),
|
|
78
|
-
httpAllowedOrigins: z.string().optional().transform((v) => v?.split(',').map((s) => s.trim()).filter(Boolean)),
|
|
79
|
-
httpAllowedHosts: z.string().optional().transform((v) => v?.split(',').map((s) => s.trim()).filter(Boolean)),
|
|
80
|
-
|
|
81
|
-
// SSE Event Subscription Configuration
|
|
82
|
-
sseEventsEnabled: createBooleanStringSchema(true),
|
|
83
|
-
ownerQuestionsEnabled: createBooleanStringSchema(true),
|
|
84
|
-
ownerQuestionsHaUsers: z.string().optional(),
|
|
85
|
-
sseEventsPath: z.string().optional(),
|
|
86
|
-
|
|
87
|
-
// Rate Limiting Configuration
|
|
88
|
-
rateLimitEnabled: createBooleanStringSchema(true),
|
|
89
|
-
rateLimitWindowMs: numericStringSchema,
|
|
90
|
-
rateLimitMaxRequests: numericStringSchema,
|
|
91
|
-
// M4: immediate-peer IPs whose forwarding headers may be trusted (CSV).
|
|
92
|
-
rateLimitTrustedProxies: z
|
|
93
|
-
.string()
|
|
94
|
-
.optional()
|
|
95
|
-
.transform((v) => v?.split(',').map((s) => s.trim()).filter(Boolean)),
|
|
96
|
-
|
|
97
|
-
// Authentication Configuration
|
|
98
|
-
authMethod: z.enum(['none', 'bearer']).optional().default('none'),
|
|
99
|
-
authSecret: z.string().optional(),
|
|
100
|
-
authRequireExp: createBooleanStringSchema(false),
|
|
101
|
-
authIssuer: z.string().min(1).optional(),
|
|
102
|
-
authAudience: z.string().min(1).optional(),
|
|
103
|
-
permissionsConfig: z.string().optional(),
|
|
104
|
-
})
|
|
105
|
-
.refine(
|
|
106
|
-
(data) => {
|
|
107
|
-
if (data.httpBindAddr && !isValidBindAddress(data.httpBindAddr)) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
return true;
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
message: 'MCP_HTTP_BIND_ADDR must be a valid IPv4 or IPv6 address',
|
|
114
|
-
path: ['httpBindAddr'],
|
|
115
|
-
}
|
|
116
|
-
)
|
|
117
|
-
// zod 4 removed refine()'s message-as-function form; superRefine reports the same
|
|
118
|
-
// message and path, naming the first invalid origin.
|
|
119
|
-
.superRefine((data, ctx) => {
|
|
120
|
-
const invalidOrigin = data.httpAllowedOrigins?.find((origin) => !isValidOrigin(origin));
|
|
121
|
-
if (invalidOrigin !== undefined) {
|
|
122
|
-
ctx.addIssue({
|
|
123
|
-
code: 'custom',
|
|
124
|
-
message: `MCP_HTTP_ALLOWED_ORIGINS contains invalid origin: ${invalidOrigin}`,
|
|
125
|
-
path: ['httpAllowedOrigins'],
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
.refine(
|
|
130
|
-
(data) => {
|
|
131
|
-
if (data.authMethod === 'bearer' && !data.authSecret) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
return true;
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
message: 'MCP_AUTH_SECRET is required when MCP_AUTH_METHOD is "bearer"',
|
|
138
|
-
path: ['authSecret'],
|
|
139
|
-
}
|
|
140
|
-
)
|
|
141
|
-
.refine(
|
|
142
|
-
(data) => {
|
|
143
|
-
// M8: a weak signing secret undermines the whole bearer scheme.
|
|
144
|
-
if (data.authMethod === 'bearer' && data.authSecret && data.authSecret.length < 32) {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
return true;
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
message: 'MCP_AUTH_SECRET must be at least 32 characters when MCP_AUTH_METHOD is "bearer"',
|
|
151
|
-
path: ['authSecret'],
|
|
152
|
-
}
|
|
153
|
-
)
|
|
154
|
-
.refine(
|
|
155
|
-
(data) => {
|
|
156
|
-
// H5: an unauthenticated server must never be reachable off-host. Allow
|
|
157
|
-
// method=none only when the (explicit) bind address is loopback. A missing
|
|
158
|
-
// bind address defaults to 127.0.0.1 later, so it is safe.
|
|
159
|
-
if (
|
|
160
|
-
data.authMethod === 'none' &&
|
|
161
|
-
data.httpBindAddr &&
|
|
162
|
-
!isLoopbackAddress(data.httpBindAddr)
|
|
163
|
-
) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
return true;
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
message:
|
|
170
|
-
'MCP_AUTH_METHOD=none is only permitted on a loopback bind address ' +
|
|
171
|
-
'(127.0.0.0/8 or ::1). Set MCP_AUTH_METHOD=bearer (with MCP_AUTH_SECRET) ' +
|
|
172
|
-
'to bind a public interface such as 0.0.0.0.',
|
|
173
|
-
path: ['authMethod'],
|
|
174
|
-
}
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
export interface EnvironmentConfig {
|
|
178
|
-
// Plugin Enable Flags
|
|
179
|
-
haPluginEnabled: boolean;
|
|
180
|
-
aiPluginEnabled: boolean;
|
|
181
|
-
omadaPluginEnabled: boolean;
|
|
182
|
-
|
|
183
|
-
// Home Assistant Client Configuration (optional if using Omada only)
|
|
184
|
-
baseUrl?: string;
|
|
185
|
-
token?: string;
|
|
186
|
-
strictSsl: boolean;
|
|
187
|
-
timeout: number;
|
|
188
|
-
|
|
189
|
-
// Omada Client Configuration (optional)
|
|
190
|
-
omadaBaseUrl?: string;
|
|
191
|
-
omadaClientId?: string;
|
|
192
|
-
omadaClientSecret?: string;
|
|
193
|
-
omadacId?: string;
|
|
194
|
-
siteId?: string;
|
|
195
|
-
omadaStrictSsl: boolean;
|
|
196
|
-
requestTimeout?: number;
|
|
197
|
-
|
|
198
|
-
// AI Provider Configuration
|
|
199
|
-
aiProvider: AIProviderType;
|
|
200
|
-
aiUrl: string;
|
|
201
|
-
aiModel: string;
|
|
202
|
-
aiTimeout: number;
|
|
203
|
-
aiApiKey?: string;
|
|
204
|
-
|
|
205
|
-
// Pre-registered REST actions for the invokeAction tool (empty = tool not registered)
|
|
206
|
-
restActions: Record<string, RestAction>;
|
|
207
|
-
|
|
208
|
-
// Chat-face (OpenAPI/REST) tool slice (undefined = legacy 8-tool default)
|
|
209
|
-
chatTools?: ChatToolsSlice;
|
|
210
|
-
|
|
211
|
-
// MCP Generic Server Configuration
|
|
212
|
-
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
|
213
|
-
logFormat: 'plain' | 'json' | 'gcp-json';
|
|
214
|
-
useHttp: boolean;
|
|
215
|
-
stateful: boolean;
|
|
216
|
-
toolRegistrationMode: 'eager' | 'graph';
|
|
217
|
-
|
|
218
|
-
// MCP Server HTTP Configuration
|
|
219
|
-
httpPort?: number;
|
|
220
|
-
httpBindAddr?: string;
|
|
221
|
-
httpPath?: string;
|
|
222
|
-
httpEnableHealthcheck: boolean;
|
|
223
|
-
httpHealthcheckPath?: string;
|
|
224
|
-
httpAllowCors: boolean;
|
|
225
|
-
httpAllowedOrigins?: string[];
|
|
226
|
-
/**
|
|
227
|
-
* Opt-in list of Host-header values (host[:port]) the server is reached by.
|
|
228
|
-
* When set, DNS-rebinding Host validation is enforced at the transport (M3);
|
|
229
|
-
* when unset, Host validation is OFF (origin/CORS is handled separately).
|
|
230
|
-
*/
|
|
231
|
-
httpAllowedHosts?: string[];
|
|
232
|
-
|
|
233
|
-
// SSE Event Subscription Configuration
|
|
234
|
-
sseEventsEnabled: boolean;
|
|
235
|
-
/** askOwner / getOwnerAnswer / cancelOwnerQuestion (HTTP mode with Home Assistant). */
|
|
236
|
-
ownerQuestionsEnabled: boolean;
|
|
237
|
-
/** HA user ids allowed to answer owner questions; empty = any HA user. */
|
|
238
|
-
ownerQuestionsHaUsers: string[];
|
|
239
|
-
sseEventsPath: string;
|
|
240
|
-
|
|
241
|
-
// Rate Limiting Configuration
|
|
242
|
-
rateLimitEnabled: boolean;
|
|
243
|
-
rateLimitWindowMs: number;
|
|
244
|
-
rateLimitMaxRequests: number;
|
|
245
|
-
rateLimitTrustedProxies?: string[];
|
|
246
|
-
|
|
247
|
-
// Authentication Configuration
|
|
248
|
-
authMethod: 'none' | 'bearer';
|
|
249
|
-
authSecret?: string;
|
|
250
|
-
authRequireExp: boolean;
|
|
251
|
-
authIssuer?: string;
|
|
252
|
-
authAudience?: string;
|
|
253
|
-
permissions: PermissionsConfig;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function loadConfig(env: NodeJS.ProcessEnv = process.env): EnvironmentConfig {
|
|
257
|
-
const parsed = envSchema.safeParse({
|
|
258
|
-
// Plugin Enable Flags
|
|
259
|
-
haPluginEnabled: env.HA_PLUGIN_ENABLED,
|
|
260
|
-
aiPluginEnabled: env.AI_PLUGIN_ENABLED,
|
|
261
|
-
omadaPluginEnabled: env.OMADA_PLUGIN_ENABLED,
|
|
262
|
-
|
|
263
|
-
// Home Assistant Client Configuration
|
|
264
|
-
haUrl: env.HA_URL,
|
|
265
|
-
haToken: env.HA_TOKEN,
|
|
266
|
-
haStrictSsl: env.HA_STRICT_SSL,
|
|
267
|
-
haTimeout: env.HA_TIMEOUT,
|
|
268
|
-
|
|
269
|
-
// Omada Client Configuration
|
|
270
|
-
omadaBaseUrl: env.OMADA_BASE_URL,
|
|
271
|
-
omadaClientId: env.OMADA_CLIENT_ID,
|
|
272
|
-
omadaClientSecret: env.OMADA_CLIENT_SECRET,
|
|
273
|
-
omadaOmadacId: env.OMADA_OMADAC_ID,
|
|
274
|
-
omadaSiteId: env.OMADA_SITE_ID,
|
|
275
|
-
omadaStrictSsl: env.OMADA_STRICT_SSL,
|
|
276
|
-
omadaTimeout: env.OMADA_TIMEOUT,
|
|
277
|
-
|
|
278
|
-
// Pre-registered REST actions
|
|
279
|
-
restActions: env.MCP_REST_ACTIONS,
|
|
280
|
-
|
|
281
|
-
// Chat-face tool slice
|
|
282
|
-
chatTools: env.MCP_CHAT_TOOLS,
|
|
283
|
-
|
|
284
|
-
// AI Provider Configuration
|
|
285
|
-
aiProvider: env.AI_PROVIDER,
|
|
286
|
-
aiUrl: env.AI_URL,
|
|
287
|
-
aiModel: env.AI_MODEL,
|
|
288
|
-
aiTimeout: env.AI_TIMEOUT,
|
|
289
|
-
aiApiKey: env.AI_API_KEY,
|
|
290
|
-
|
|
291
|
-
// MCP Generic Server Configuration
|
|
292
|
-
logLevel: env.MCP_SERVER_LOG_LEVEL,
|
|
293
|
-
logFormat: env.MCP_SERVER_LOG_FORMAT,
|
|
294
|
-
useHttp: env.MCP_SERVER_USE_HTTP,
|
|
295
|
-
stateful: env.MCP_SERVER_STATEFUL,
|
|
296
|
-
toolRegistrationMode: env.MCP_TOOL_REGISTRATION_MODE,
|
|
297
|
-
|
|
298
|
-
// MCP Server HTTP Configuration
|
|
299
|
-
httpPort: env.MCP_HTTP_PORT,
|
|
300
|
-
httpBindAddr: env.MCP_HTTP_BIND_ADDR,
|
|
301
|
-
httpPath: env.MCP_HTTP_PATH,
|
|
302
|
-
httpEnableHealthcheck: env.MCP_HTTP_ENABLE_HEALTHCHECK,
|
|
303
|
-
httpHealthcheckPath: env.MCP_HTTP_HEALTHCHECK_PATH,
|
|
304
|
-
httpAllowCors: env.MCP_HTTP_ALLOW_CORS,
|
|
305
|
-
httpAllowedOrigins: env.MCP_HTTP_ALLOWED_ORIGINS,
|
|
306
|
-
httpAllowedHosts: env.MCP_HTTP_ALLOWED_HOSTS,
|
|
307
|
-
|
|
308
|
-
// SSE Event Subscription Configuration
|
|
309
|
-
sseEventsEnabled: env.MCP_SSE_EVENTS_ENABLED,
|
|
310
|
-
ownerQuestionsEnabled: env.MCP_OWNER_QUESTIONS_ENABLED,
|
|
311
|
-
ownerQuestionsHaUsers: env.MCP_OWNER_QUESTIONS_HA_USERS,
|
|
312
|
-
sseEventsPath: env.MCP_SSE_EVENTS_PATH,
|
|
313
|
-
|
|
314
|
-
// Rate Limiting Configuration
|
|
315
|
-
rateLimitEnabled: env.MCP_RATE_LIMIT_ENABLED,
|
|
316
|
-
rateLimitWindowMs: env.MCP_RATE_LIMIT_WINDOW_MS,
|
|
317
|
-
rateLimitMaxRequests: env.MCP_RATE_LIMIT_MAX_REQUESTS,
|
|
318
|
-
rateLimitTrustedProxies: env.MCP_RATE_LIMIT_TRUSTED_PROXIES,
|
|
319
|
-
|
|
320
|
-
// Authentication Configuration
|
|
321
|
-
authMethod: env.MCP_AUTH_METHOD,
|
|
322
|
-
authSecret: env.MCP_AUTH_SECRET,
|
|
323
|
-
authRequireExp: env.MCP_AUTH_REQUIRE_EXP,
|
|
324
|
-
authIssuer: env.MCP_AUTH_ISSUER,
|
|
325
|
-
authAudience: env.MCP_AUTH_AUDIENCE,
|
|
326
|
-
permissionsConfig: env.MCP_PERMISSIONS_CONFIG,
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
if (!parsed.success) {
|
|
330
|
-
const messages = parsed.error.issues.map((issue: z.ZodIssue) => issue.message);
|
|
331
|
-
throw new Error(`Invalid environment configuration:\n${messages.join('\n')}`);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const httpPath = parsed.data.httpPath ?? '/mcp';
|
|
335
|
-
const httpBindAddr = parsed.data.httpBindAddr ?? '127.0.0.1';
|
|
336
|
-
const httpPort = parsed.data.httpPort ?? 3000;
|
|
337
|
-
|
|
338
|
-
// M3: default origins must be full scheme://host:port so they actually match a
|
|
339
|
-
// real browser `Origin` header (the SDK compares it by exact string). Bare
|
|
340
|
-
// hostnames like "localhost" never match "http://localhost:3000" and so left
|
|
341
|
-
// DNS-rebinding/CORS protection effectively disabled for the default config.
|
|
342
|
-
let httpAllowedOrigins =
|
|
343
|
-
parsed.data.httpAllowedOrigins ??
|
|
344
|
-
[`http://localhost:${httpPort}`, `http://127.0.0.1:${httpPort}`];
|
|
345
|
-
|
|
346
|
-
if (httpAllowedOrigins.includes('*')) {
|
|
347
|
-
logger.warn('Wildcard (*) origin allowed - origin validation disabled');
|
|
348
|
-
httpAllowedOrigins = [];
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// H5: make the no-auth posture loud even when the bind is loopback.
|
|
352
|
-
if (parsed.data.authMethod === 'none') {
|
|
353
|
-
logger.warn(
|
|
354
|
-
'MCP_AUTH_METHOD=none: authentication is DISABLED. This is only permitted ' +
|
|
355
|
-
'on a loopback bind; set MCP_AUTH_METHOD=bearer to expose the server.'
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
// Plugin Enable Flags
|
|
361
|
-
haPluginEnabled: parsed.data.haPluginEnabled,
|
|
362
|
-
aiPluginEnabled: parsed.data.aiPluginEnabled,
|
|
363
|
-
omadaPluginEnabled: parsed.data.omadaPluginEnabled,
|
|
364
|
-
|
|
365
|
-
// Home Assistant Client Configuration (optional)
|
|
366
|
-
baseUrl: parsed.data.haUrl?.replace(/\/$/, ''),
|
|
367
|
-
token: parsed.data.haToken,
|
|
368
|
-
strictSsl: parsed.data.haStrictSsl,
|
|
369
|
-
timeout: parsed.data.haTimeout ?? 30000,
|
|
370
|
-
|
|
371
|
-
// Omada Client Configuration (optional)
|
|
372
|
-
omadaBaseUrl: parsed.data.omadaBaseUrl?.replace(/\/$/, ''),
|
|
373
|
-
omadaClientId: parsed.data.omadaClientId,
|
|
374
|
-
omadaClientSecret: parsed.data.omadaClientSecret,
|
|
375
|
-
omadacId: parsed.data.omadaOmadacId,
|
|
376
|
-
siteId: parsed.data.omadaSiteId,
|
|
377
|
-
omadaStrictSsl: parsed.data.omadaStrictSsl,
|
|
378
|
-
requestTimeout: parsed.data.omadaTimeout,
|
|
379
|
-
|
|
380
|
-
// Pre-registered REST actions (throws on malformed JSON — config typos fail loudly)
|
|
381
|
-
restActions: parseRestActions(parsed.data.restActions),
|
|
382
|
-
|
|
383
|
-
// Chat-face tool slice (throws on unknown category/access — typos fail loudly)
|
|
384
|
-
chatTools: parseChatTools(parsed.data.chatTools),
|
|
385
|
-
|
|
386
|
-
// AI Provider Configuration
|
|
387
|
-
aiProvider: (parsed.data.aiProvider ?? 'ollama') as AIProviderType,
|
|
388
|
-
aiUrl: parsed.data.aiUrl ?? 'http://localhost:11434',
|
|
389
|
-
aiModel: parsed.data.aiModel ?? 'qwen3:14b',
|
|
390
|
-
aiTimeout: parsed.data.aiTimeout ?? 60000,
|
|
391
|
-
aiApiKey: parsed.data.aiApiKey,
|
|
392
|
-
|
|
393
|
-
// MCP Generic Server Configuration
|
|
394
|
-
logLevel: parsed.data.logLevel,
|
|
395
|
-
logFormat: parsed.data.logFormat,
|
|
396
|
-
useHttp: parsed.data.useHttp,
|
|
397
|
-
stateful: parsed.data.stateful,
|
|
398
|
-
toolRegistrationMode: parsed.data.toolRegistrationMode,
|
|
399
|
-
|
|
400
|
-
// MCP Server HTTP Configuration
|
|
401
|
-
httpPort,
|
|
402
|
-
httpBindAddr,
|
|
403
|
-
httpPath,
|
|
404
|
-
httpEnableHealthcheck: parsed.data.httpEnableHealthcheck,
|
|
405
|
-
httpHealthcheckPath: parsed.data.httpHealthcheckPath,
|
|
406
|
-
httpAllowCors: parsed.data.httpAllowCors,
|
|
407
|
-
httpAllowedOrigins,
|
|
408
|
-
httpAllowedHosts: parsed.data.httpAllowedHosts,
|
|
409
|
-
|
|
410
|
-
// SSE Event Subscription Configuration
|
|
411
|
-
sseEventsEnabled: parsed.data.sseEventsEnabled,
|
|
412
|
-
ownerQuestionsEnabled: parsed.data.ownerQuestionsEnabled,
|
|
413
|
-
ownerQuestionsHaUsers: (parsed.data.ownerQuestionsHaUsers ?? '').split(',').map((u) => u.trim()).filter(Boolean),
|
|
414
|
-
sseEventsPath: parsed.data.sseEventsPath ?? '/subscribe_events',
|
|
415
|
-
|
|
416
|
-
// Rate Limiting Configuration
|
|
417
|
-
rateLimitEnabled: parsed.data.rateLimitEnabled,
|
|
418
|
-
rateLimitWindowMs: parsed.data.rateLimitWindowMs ?? 60000,
|
|
419
|
-
rateLimitMaxRequests: parsed.data.rateLimitMaxRequests ?? 100,
|
|
420
|
-
rateLimitTrustedProxies: parsed.data.rateLimitTrustedProxies,
|
|
421
|
-
|
|
422
|
-
// Authentication Configuration
|
|
423
|
-
authMethod: parsed.data.authMethod,
|
|
424
|
-
authSecret: parsed.data.authSecret,
|
|
425
|
-
authRequireExp: parsed.data.authRequireExp,
|
|
426
|
-
authIssuer: parsed.data.authIssuer,
|
|
427
|
-
authAudience: parsed.data.authAudience,
|
|
428
|
-
permissions: parsePermissionsConfig(parsed.data.permissionsConfig),
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export function validateConfig(config: EnvironmentConfig): void {
|
|
433
|
-
if (!config.baseUrl || !config.token) {
|
|
434
|
-
throw new Error('Invalid configuration: missing baseUrl or token');
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
export function loadConfigFromEnv(): EnvironmentConfig {
|
|
439
|
-
return loadConfig();
|
|
440
|
-
}
|