@coffeerunhobby/mcp-ha-connect 0.5.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.
- package/.dockerignore +8 -0
- package/.env.example +149 -0
- package/.gitattributes +30 -0
- package/Dockerfile +33 -0
- package/LICENSE +21 -0
- package/README.md +394 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +183 -0
- package/dist/config.js.map +1 -0
- package/dist/haClient/automations.d.ts +67 -0
- package/dist/haClient/automations.d.ts.map +1 -0
- package/dist/haClient/automations.js +181 -0
- package/dist/haClient/automations.js.map +1 -0
- package/dist/haClient/config.d.ts +28 -0
- package/dist/haClient/config.d.ts.map +1 -0
- package/dist/haClient/config.js +38 -0
- package/dist/haClient/config.js.map +1 -0
- package/dist/haClient/devices.d.ts +219 -0
- package/dist/haClient/devices.d.ts.map +1 -0
- package/dist/haClient/devices.js +506 -0
- package/dist/haClient/devices.js.map +1 -0
- package/dist/haClient/entities.d.ts +35 -0
- package/dist/haClient/entities.d.ts.map +1 -0
- package/dist/haClient/entities.js +97 -0
- package/dist/haClient/entities.js.map +1 -0
- package/dist/haClient/events.d.ts +138 -0
- package/dist/haClient/events.d.ts.map +1 -0
- package/dist/haClient/events.js +344 -0
- package/dist/haClient/events.js.map +1 -0
- package/dist/haClient/history.d.ts +26 -0
- package/dist/haClient/history.d.ts.map +1 -0
- package/dist/haClient/history.js +54 -0
- package/dist/haClient/history.js.map +1 -0
- package/dist/haClient/index.d.ts +153 -0
- package/dist/haClient/index.d.ts.map +1 -0
- package/dist/haClient/index.js +223 -0
- package/dist/haClient/index.js.map +1 -0
- package/dist/haClient/request.d.ts +34 -0
- package/dist/haClient/request.d.ts.map +1 -0
- package/dist/haClient/request.js +93 -0
- package/dist/haClient/request.js.map +1 -0
- package/dist/haClient/services.d.ts +22 -0
- package/dist/haClient/services.d.ts.map +1 -0
- package/dist/haClient/services.js +39 -0
- package/dist/haClient/services.js.map +1 -0
- package/dist/haClient/states.d.ts +26 -0
- package/dist/haClient/states.d.ts.map +1 -0
- package/dist/haClient/states.js +53 -0
- package/dist/haClient/states.js.map +1 -0
- package/dist/haClient/updates.d.ts +22 -0
- package/dist/haClient/updates.d.ts.map +1 -0
- package/dist/haClient/updates.js +48 -0
- package/dist/haClient/updates.js.map +1 -0
- package/dist/haClient.d.ts +97 -0
- package/dist/haClient.d.ts.map +1 -0
- package/dist/haClient.js +344 -0
- package/dist/haClient.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/dist/localAI/index.d.ts +37 -0
- package/dist/localAI/index.d.ts.map +1 -0
- package/dist/localAI/index.js +73 -0
- package/dist/localAI/index.js.map +1 -0
- package/dist/localAI/prompts.d.ts +20 -0
- package/dist/localAI/prompts.d.ts.map +1 -0
- package/dist/localAI/prompts.js +48 -0
- package/dist/localAI/prompts.js.map +1 -0
- package/dist/localAI/providers/base.d.ts +51 -0
- package/dist/localAI/providers/base.d.ts.map +1 -0
- package/dist/localAI/providers/base.js +80 -0
- package/dist/localAI/providers/base.js.map +1 -0
- package/dist/localAI/providers/ollama.d.ts +22 -0
- package/dist/localAI/providers/ollama.d.ts.map +1 -0
- package/dist/localAI/providers/ollama.js +63 -0
- package/dist/localAI/providers/ollama.js.map +1 -0
- package/dist/localAI/providers/openai.d.ts +23 -0
- package/dist/localAI/providers/openai.d.ts.map +1 -0
- package/dist/localAI/providers/openai.js +63 -0
- package/dist/localAI/providers/openai.js.map +1 -0
- package/dist/localAI/types.d.ts +61 -0
- package/dist/localAI/types.d.ts.map +1 -0
- package/dist/localAI/types.js +6 -0
- package/dist/localAI/types.js.map +1 -0
- package/dist/ollamaClient.d.ts +37 -0
- package/dist/ollamaClient.d.ts.map +1 -0
- package/dist/ollamaClient.js +110 -0
- package/dist/ollamaClient.js.map +1 -0
- package/dist/resources/index.d.ts +11 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +238 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server/common.d.ts +9 -0
- package/dist/server/common.d.ts.map +1 -0
- package/dist/server/common.js +27 -0
- package/dist/server/common.js.map +1 -0
- package/dist/server/eventSubscription.d.ts +53 -0
- package/dist/server/eventSubscription.d.ts.map +1 -0
- package/dist/server/eventSubscription.js +257 -0
- package/dist/server/eventSubscription.js.map +1 -0
- package/dist/server/http.d.ts +12 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +491 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/rateLimiter.d.ts +70 -0
- package/dist/server/rateLimiter.d.ts.map +1 -0
- package/dist/server/rateLimiter.js +156 -0
- package/dist/server/rateLimiter.js.map +1 -0
- package/dist/server/sse.d.ts +32 -0
- package/dist/server/sse.d.ts.map +1 -0
- package/dist/server/sse.js +76 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/server/stdio.d.ts +8 -0
- package/dist/server/stdio.d.ts.map +1 -0
- package/dist/server/stdio.js +16 -0
- package/dist/server/stdio.js.map +1 -0
- package/dist/server/stream.d.ts +26 -0
- package/dist/server/stream.d.ts.map +1 -0
- package/dist/server/stream.js +81 -0
- package/dist/server/stream.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +30 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/callService.d.ts +8 -0
- package/dist/tools/callService.d.ts.map +1 -0
- package/dist/tools/callService.js +100 -0
- package/dist/tools/callService.js.map +1 -0
- package/dist/tools/getClient.d.ts +8 -0
- package/dist/tools/getClient.d.ts.map +1 -0
- package/dist/tools/getClient.js +94 -0
- package/dist/tools/getClient.js.map +1 -0
- package/dist/tools/getDevice.d.ts +8 -0
- package/dist/tools/getDevice.d.ts.map +1 -0
- package/dist/tools/getDevice.js +94 -0
- package/dist/tools/getDevice.js.map +1 -0
- package/dist/tools/getEntitiesByDomain.d.ts +8 -0
- package/dist/tools/getEntitiesByDomain.d.ts.map +1 -0
- package/dist/tools/getEntitiesByDomain.js +80 -0
- package/dist/tools/getEntitiesByDomain.js.map +1 -0
- package/dist/tools/getState.d.ts +8 -0
- package/dist/tools/getState.d.ts.map +1 -0
- package/dist/tools/getState.js +90 -0
- package/dist/tools/getState.js.map +1 -0
- package/dist/tools/getStates.d.ts +8 -0
- package/dist/tools/getStates.d.ts.map +1 -0
- package/dist/tools/getStates.js +70 -0
- package/dist/tools/getStates.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +9 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/listClients.d.ts +8 -0
- package/dist/tools/listClients.d.ts.map +1 -0
- package/dist/tools/listClients.js +73 -0
- package/dist/tools/listClients.js.map +1 -0
- package/dist/tools/listDevices.d.ts +8 -0
- package/dist/tools/listDevices.d.ts.map +1 -0
- package/dist/tools/listDevices.js +73 -0
- package/dist/tools/listDevices.js.map +1 -0
- package/dist/tools/listSites.d.ts +8 -0
- package/dist/tools/listSites.d.ts.map +1 -0
- package/dist/tools/listSites.js +67 -0
- package/dist/tools/listSites.js.map +1 -0
- package/dist/tools/registry.d.ts +9 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +1382 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/searchDevices.d.ts +8 -0
- package/dist/tools/searchDevices.d.ts.map +1 -0
- package/dist/tools/searchDevices.js +84 -0
- package/dist/tools/searchDevices.js.map +1 -0
- package/dist/tools/searchEntities.d.ts +8 -0
- package/dist/tools/searchEntities.d.ts.map +1 -0
- package/dist/tools/searchEntities.js +80 -0
- package/dist/tools/searchEntities.js.map +1 -0
- package/dist/types/entity.d.ts +98 -0
- package/dist/types/entity.d.ts.map +1 -0
- package/dist/types/entity.js +2 -0
- package/dist/types/entity.js.map +1 -0
- package/dist/types/errors.d.ts +16 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +32 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/haConfig.d.ts +8 -0
- package/dist/types/haConfig.d.ts.map +1 -0
- package/dist/types/haConfig.js +2 -0
- package/dist/types/haConfig.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/request.d.ts +7 -0
- package/dist/types/request.d.ts.map +1 -0
- package/dist/types/request.js +2 -0
- package/dist/types/request.js.map +1 -0
- package/dist/types/serviceCall.d.ts +18 -0
- package/dist/types/serviceCall.d.ts.map +1 -0
- package/dist/types/serviceCall.js +2 -0
- package/dist/types/serviceCall.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/config-validations.d.ts +12 -0
- package/dist/utils/config-validations.d.ts.map +1 -0
- package/dist/utils/config-validations.js +43 -0
- package/dist/utils/config-validations.js.map +1 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +94 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wrapper.d.ts +8 -0
- package/dist/wrapper.d.ts.map +1 -0
- package/dist/wrapper.js +301 -0
- package/dist/wrapper.js.map +1 -0
- package/docker-compose.yml +17 -0
- package/docs/CHANGELOG.md +86 -0
- package/docs/LOCAL_CLIENT.md +268 -0
- package/docs/SSE_API.md +341 -0
- package/eslint.config.js +33 -0
- package/n8n/ai-agent-safety-monitor.json +355 -0
- package/openapi.json +166 -0
- package/package.json +51 -0
- package/src/config.ts +260 -0
- package/src/haClient/automations.ts +217 -0
- package/src/haClient/config.ts +41 -0
- package/src/haClient/devices.ts +653 -0
- package/src/haClient/entities.ts +128 -0
- package/src/haClient/events.ts +456 -0
- package/src/haClient/history.ts +68 -0
- package/src/haClient/index.ts +287 -0
- package/src/haClient/request.ts +127 -0
- package/src/haClient/services.ts +45 -0
- package/src/haClient/states.ts +60 -0
- package/src/haClient/updates.ts +58 -0
- package/src/index.ts +99 -0
- package/src/localAI/index.ts +88 -0
- package/src/localAI/prompts.ts +50 -0
- package/src/localAI/providers/base.ts +120 -0
- package/src/localAI/providers/ollama.ts +78 -0
- package/src/localAI/providers/openai.ts +86 -0
- package/src/localAI/types.ts +67 -0
- package/src/resources/index.ts +266 -0
- package/src/server/common.ts +38 -0
- package/src/server/eventSubscription.ts +318 -0
- package/src/server/http.ts +546 -0
- package/src/server/rateLimiter.ts +195 -0
- package/src/server/sse.ts +120 -0
- package/src/server/stdio.ts +22 -0
- package/src/server/stream.ts +113 -0
- package/src/tools/callService.ts +114 -0
- package/src/tools/getEntitiesByDomain.ts +92 -0
- package/src/tools/getState.ts +103 -0
- package/src/tools/getStates.ts +80 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/registry.ts +1521 -0
- package/src/tools/searchEntities.ts +92 -0
- package/src/types/entity.ts +110 -0
- package/src/types/errors.ts +33 -0
- package/src/types/haConfig.ts +7 -0
- package/src/types/index.ts +5 -0
- package/src/types/request.ts +6 -0
- package/src/types/serviceCall.ts +18 -0
- package/src/types.ts +2 -0
- package/src/utils/config-validations.ts +46 -0
- package/src/utils/logger.ts +114 -0
- package/src/wrapper.ts +340 -0
- package/tests/config.test.ts +332 -0
- package/tests/haClient/automations.test.ts +133 -0
- package/tests/haClient/config.test.ts +96 -0
- package/tests/haClient/entities.test.ts +188 -0
- package/tests/haClient/history.test.ts +170 -0
- package/tests/haClient/index.test.ts +221 -0
- package/tests/haClient/request.test.ts +217 -0
- package/tests/haClient/services.test.ts +120 -0
- package/tests/haClient/states.test.ts +119 -0
- package/tests/haClient/updates.test.ts +193 -0
- package/tests/index.test.ts +164 -0
- package/tests/localAI/index.test.ts +168 -0
- package/tests/localAI/ollama.test.ts +187 -0
- package/tests/localAI/openai.test.ts +257 -0
- package/tests/server/common.test.ts +64 -0
- package/tests/server/http.test.ts +326 -0
- package/tests/server/sse.test.ts +240 -0
- package/tests/server/stdio.test.ts +102 -0
- package/tests/server/stream.test.ts +287 -0
- package/tests/tools/registry.test.ts +355 -0
- package/tests/utils/config-validations.test.ts +289 -0
- package/tests/utils/logger.test.ts +327 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Entities Tool
|
|
3
|
+
* Search for entities by name or entity_id
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import {
|
|
8
|
+
CallToolRequestSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
+
import type { HaClient } from '../haClient/index.js';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
13
|
+
|
|
14
|
+
export function registerSearchEntitiesTool(server: Server, client: HaClient): void {
|
|
15
|
+
logger.debug('Registering searchEntities tool');
|
|
16
|
+
|
|
17
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
18
|
+
return {
|
|
19
|
+
tools: [
|
|
20
|
+
{
|
|
21
|
+
name: 'searchEntities',
|
|
22
|
+
description: 'Search for entities by name or entity_id. Returns all matching entities.',
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
query: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Search query (searches in entity_id and friendly_name)',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
required: ['query'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
39
|
+
if (request.params.name !== 'searchEntities') {
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: 'text',
|
|
44
|
+
text: JSON.stringify({ error: 'Unknown tool' }),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
isError: true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const { query } = request.params.arguments as { query: string };
|
|
53
|
+
|
|
54
|
+
if (!query) {
|
|
55
|
+
throw new Error('query is required');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logger.info('Executing searchEntities tool', { query });
|
|
59
|
+
const entities = await client.searchEntities(query);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: JSON.stringify({
|
|
66
|
+
query,
|
|
67
|
+
count: entities.length,
|
|
68
|
+
entities,
|
|
69
|
+
}, null, 2),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger.error('Failed to search entities', {
|
|
75
|
+
error: error instanceof Error ? error.message : String(error),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: JSON.stringify({
|
|
83
|
+
error: 'Failed to search entities',
|
|
84
|
+
message: error instanceof Error ? error.message : String(error),
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export interface Entity {
|
|
2
|
+
entity_id: string;
|
|
3
|
+
state: string;
|
|
4
|
+
attributes: Record<string, unknown>;
|
|
5
|
+
last_changed: string;
|
|
6
|
+
last_updated: string;
|
|
7
|
+
context: {
|
|
8
|
+
id: string;
|
|
9
|
+
parent_id: string | null;
|
|
10
|
+
user_id: string | null;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface EntityListResponse {
|
|
15
|
+
entities: Entity[];
|
|
16
|
+
total: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Automation {
|
|
20
|
+
id: string;
|
|
21
|
+
alias: string;
|
|
22
|
+
state: 'on' | 'off';
|
|
23
|
+
last_triggered: string | null;
|
|
24
|
+
description?: string;
|
|
25
|
+
mode?: 'single' | 'restart' | 'queued' | 'parallel';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AutomationTrigger {
|
|
29
|
+
platform: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AutomationCondition {
|
|
34
|
+
condition: string;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AutomationAction {
|
|
39
|
+
service?: string;
|
|
40
|
+
target?: {
|
|
41
|
+
entity_id?: string | string[];
|
|
42
|
+
device_id?: string | string[];
|
|
43
|
+
area_id?: string | string[];
|
|
44
|
+
};
|
|
45
|
+
data?: Record<string, unknown>;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AutomationConfig {
|
|
50
|
+
alias: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
mode?: 'single' | 'restart' | 'queued' | 'parallel';
|
|
53
|
+
trigger: AutomationTrigger | AutomationTrigger[];
|
|
54
|
+
condition?: AutomationCondition | AutomationCondition[];
|
|
55
|
+
action: AutomationAction | AutomationAction[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface DomainSummary {
|
|
59
|
+
domain: string;
|
|
60
|
+
count: number;
|
|
61
|
+
states: Record<string, number>;
|
|
62
|
+
entities: Array<{
|
|
63
|
+
entity_id: string;
|
|
64
|
+
state: string;
|
|
65
|
+
friendly_name?: string;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface HaVersion {
|
|
70
|
+
version: string;
|
|
71
|
+
location_name?: string;
|
|
72
|
+
config_dir?: string;
|
|
73
|
+
time_zone?: string;
|
|
74
|
+
unit_system?: Record<string, string>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface LogbookEntry {
|
|
78
|
+
when: string;
|
|
79
|
+
name: string;
|
|
80
|
+
message?: string;
|
|
81
|
+
entity_id?: string;
|
|
82
|
+
state?: string;
|
|
83
|
+
domain?: string;
|
|
84
|
+
context_user_id?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface UpdateInfo {
|
|
88
|
+
update_type: 'core' | 'supervisor' | 'os' | 'addon';
|
|
89
|
+
name: string;
|
|
90
|
+
version_current: string;
|
|
91
|
+
version_latest: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SupervisorCoreInfo {
|
|
95
|
+
version: string;
|
|
96
|
+
version_latest?: string;
|
|
97
|
+
update_available?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface SupervisorInfo {
|
|
101
|
+
version: string;
|
|
102
|
+
version_latest?: string;
|
|
103
|
+
update_available?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SupervisorOsInfo {
|
|
107
|
+
version: string;
|
|
108
|
+
version_latest?: string;
|
|
109
|
+
update_available?: boolean;
|
|
110
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class HaError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
message: string,
|
|
4
|
+
public code?: string,
|
|
5
|
+
public statusCode?: number,
|
|
6
|
+
public details?: unknown
|
|
7
|
+
) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'HaError';
|
|
10
|
+
Error.captureStackTrace(this, this.constructor);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class AuthenticationError extends HaError {
|
|
15
|
+
constructor(message: string, details?: unknown) {
|
|
16
|
+
super(message, 'AUTH_ERROR', 401, details);
|
|
17
|
+
this.name = 'AuthenticationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class ApiError extends HaError {
|
|
22
|
+
constructor(message: string, statusCode: number, details?: unknown) {
|
|
23
|
+
super(message, 'API_ERROR', statusCode, details);
|
|
24
|
+
this.name = 'ApiError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ConfigurationError extends HaError {
|
|
29
|
+
constructor(message: string, details?: unknown) {
|
|
30
|
+
super(message, 'CONFIG_ERROR', undefined, details);
|
|
31
|
+
this.name = 'ConfigurationError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { HaConfig } from './haConfig.js';
|
|
2
|
+
export type { Entity, EntityListResponse, Automation, AutomationConfig, AutomationTrigger, AutomationCondition, AutomationAction, DomainSummary, HaVersion, LogbookEntry, UpdateInfo, SupervisorCoreInfo, SupervisorInfo, SupervisorOsInfo } from './entity.js';
|
|
3
|
+
export type { ServiceCallData, ServiceCallResponse } from './serviceCall.js';
|
|
4
|
+
export type { RequestOptions } from './request.js';
|
|
5
|
+
export { HaError, AuthenticationError, ApiError, ConfigurationError } from './errors.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ServiceCallData {
|
|
2
|
+
domain: string;
|
|
3
|
+
service: string;
|
|
4
|
+
service_data?: Record<string, unknown>;
|
|
5
|
+
target?: {
|
|
6
|
+
entity_id?: string | string[];
|
|
7
|
+
device_id?: string | string[];
|
|
8
|
+
area_id?: string | string[];
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ServiceCallResponse {
|
|
13
|
+
context: {
|
|
14
|
+
id: string;
|
|
15
|
+
parent_id: string | null;
|
|
16
|
+
user_id: string | null;
|
|
17
|
+
};
|
|
18
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration validation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validates if a string is a valid IPv4 or IPv6 address
|
|
7
|
+
*/
|
|
8
|
+
export function isValidBindAddress(addr: string): boolean {
|
|
9
|
+
// IPv4 regex
|
|
10
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
11
|
+
// IPv6 regex (simplified)
|
|
12
|
+
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
13
|
+
|
|
14
|
+
if (ipv4Regex.test(addr)) {
|
|
15
|
+
// Validate each octet is 0-255
|
|
16
|
+
const octets = addr.split('.');
|
|
17
|
+
return octets.every((octet) => {
|
|
18
|
+
const num = Number.parseInt(octet, 10);
|
|
19
|
+
return num >= 0 && num <= 255;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return ipv6Regex.test(addr) || addr === '::' || addr === '::1';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validates if a string is a valid origin (for CORS)
|
|
28
|
+
*/
|
|
29
|
+
export function isValidOrigin(origin: string): boolean {
|
|
30
|
+
// Allow wildcard
|
|
31
|
+
if (origin === '*') return true;
|
|
32
|
+
|
|
33
|
+
// Allow localhost and specific hostnames
|
|
34
|
+
if (origin === 'localhost' || origin === '127.0.0.1' || origin === '::1') {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate URL format
|
|
39
|
+
try {
|
|
40
|
+
new URL(origin);
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
// If not a valid URL, check if it's just a hostname/IP
|
|
44
|
+
return /^[\w.-]+$/.test(origin) || isValidBindAddress(origin);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
|
|
3
|
+
type LogFields = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
6
|
+
|
|
7
|
+
type LogFormat = 'plain' | 'json' | 'gcp-json';
|
|
8
|
+
|
|
9
|
+
const levelToSeverity: Record<string, string> = {
|
|
10
|
+
trace: 'DEBUG',
|
|
11
|
+
debug: 'DEBUG',
|
|
12
|
+
info: 'INFO',
|
|
13
|
+
warn: 'WARNING',
|
|
14
|
+
error: 'ERROR',
|
|
15
|
+
fatal: 'CRITICAL',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let instance: pino.Logger;
|
|
19
|
+
|
|
20
|
+
function createLogger(level: LogLevel = 'info', format: LogFormat = 'plain', useStderr = false): pino.Logger {
|
|
21
|
+
const baseConfig: pino.LoggerOptions = {
|
|
22
|
+
level,
|
|
23
|
+
base: undefined,
|
|
24
|
+
messageKey: 'message',
|
|
25
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Configure formatters based on format
|
|
29
|
+
if (format === 'gcp-json') {
|
|
30
|
+
baseConfig.formatters = {
|
|
31
|
+
level(label) {
|
|
32
|
+
return { severity: levelToSeverity[label] ?? label.toUpperCase() };
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
} else if (format === 'plain') {
|
|
36
|
+
baseConfig.formatters = {
|
|
37
|
+
level(label) {
|
|
38
|
+
return { level: label.toUpperCase() };
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// For 'json' format, use default pino JSON output (no special formatters)
|
|
43
|
+
|
|
44
|
+
// When running in stdio mode, always log to stderr to avoid interfering with MCP protocol on stdout
|
|
45
|
+
return useStderr ? pino(baseConfig, process.stderr) : pino(baseConfig);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Initialize with default, will be reconfigured by calling initLogger
|
|
49
|
+
const defaultLevel = (process.env.MCP_SERVER_LOG_LEVEL as LogLevel | undefined) ?? 'info';
|
|
50
|
+
instance = createLogger(defaultLevel);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Initialize the logger with a specific log level and format.
|
|
54
|
+
* This should be called once during application startup.
|
|
55
|
+
* @param level - The minimum log level to output
|
|
56
|
+
* @param format - The output format (plain, json, or gcp-json)
|
|
57
|
+
* @param useStderr - If true, logs to stderr instead of stdout (required for stdio transport)
|
|
58
|
+
*/
|
|
59
|
+
export function initLogger(level: LogLevel, format: LogFormat = 'plain', useStderr = false): void {
|
|
60
|
+
instance = createLogger(level, format, useStderr);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set the logger level dynamically
|
|
65
|
+
* @param level - The new log level
|
|
66
|
+
*/
|
|
67
|
+
export function setLevel(level: LogLevel): void {
|
|
68
|
+
instance.level = level;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeMeta(meta?: LogFields): LogFields | undefined {
|
|
72
|
+
if (!meta) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const normalized: LogFields = {};
|
|
77
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
78
|
+
if (value instanceof Error) {
|
|
79
|
+
normalized[key] = { message: value.message, stack: value.stack };
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
normalized[key] = value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function write(level: LogLevel, message: string, meta?: LogFields): void {
|
|
90
|
+
const fields = normalizeMeta(meta);
|
|
91
|
+
if (fields) {
|
|
92
|
+
instance[level](fields, message);
|
|
93
|
+
} else {
|
|
94
|
+
instance[level](message);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const logger = {
|
|
99
|
+
debug(message: string, meta?: LogFields) {
|
|
100
|
+
write('debug', message, meta);
|
|
101
|
+
},
|
|
102
|
+
info(message: string, meta?: LogFields) {
|
|
103
|
+
write('info', message, meta);
|
|
104
|
+
},
|
|
105
|
+
warn(message: string, meta?: LogFields) {
|
|
106
|
+
write('warn', message, meta);
|
|
107
|
+
},
|
|
108
|
+
error(message: string, meta?: LogFields) {
|
|
109
|
+
write('error', message, meta);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Export types
|
|
114
|
+
export type { LogLevel, LogFormat, LogFields };
|