@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.
Files changed (294) hide show
  1. package/.dockerignore +8 -0
  2. package/.env.example +149 -0
  3. package/.gitattributes +30 -0
  4. package/Dockerfile +33 -0
  5. package/LICENSE +21 -0
  6. package/README.md +394 -0
  7. package/dist/config.d.ts +33 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +183 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/haClient/automations.d.ts +67 -0
  12. package/dist/haClient/automations.d.ts.map +1 -0
  13. package/dist/haClient/automations.js +181 -0
  14. package/dist/haClient/automations.js.map +1 -0
  15. package/dist/haClient/config.d.ts +28 -0
  16. package/dist/haClient/config.d.ts.map +1 -0
  17. package/dist/haClient/config.js +38 -0
  18. package/dist/haClient/config.js.map +1 -0
  19. package/dist/haClient/devices.d.ts +219 -0
  20. package/dist/haClient/devices.d.ts.map +1 -0
  21. package/dist/haClient/devices.js +506 -0
  22. package/dist/haClient/devices.js.map +1 -0
  23. package/dist/haClient/entities.d.ts +35 -0
  24. package/dist/haClient/entities.d.ts.map +1 -0
  25. package/dist/haClient/entities.js +97 -0
  26. package/dist/haClient/entities.js.map +1 -0
  27. package/dist/haClient/events.d.ts +138 -0
  28. package/dist/haClient/events.d.ts.map +1 -0
  29. package/dist/haClient/events.js +344 -0
  30. package/dist/haClient/events.js.map +1 -0
  31. package/dist/haClient/history.d.ts +26 -0
  32. package/dist/haClient/history.d.ts.map +1 -0
  33. package/dist/haClient/history.js +54 -0
  34. package/dist/haClient/history.js.map +1 -0
  35. package/dist/haClient/index.d.ts +153 -0
  36. package/dist/haClient/index.d.ts.map +1 -0
  37. package/dist/haClient/index.js +223 -0
  38. package/dist/haClient/index.js.map +1 -0
  39. package/dist/haClient/request.d.ts +34 -0
  40. package/dist/haClient/request.d.ts.map +1 -0
  41. package/dist/haClient/request.js +93 -0
  42. package/dist/haClient/request.js.map +1 -0
  43. package/dist/haClient/services.d.ts +22 -0
  44. package/dist/haClient/services.d.ts.map +1 -0
  45. package/dist/haClient/services.js +39 -0
  46. package/dist/haClient/services.js.map +1 -0
  47. package/dist/haClient/states.d.ts +26 -0
  48. package/dist/haClient/states.d.ts.map +1 -0
  49. package/dist/haClient/states.js +53 -0
  50. package/dist/haClient/states.js.map +1 -0
  51. package/dist/haClient/updates.d.ts +22 -0
  52. package/dist/haClient/updates.d.ts.map +1 -0
  53. package/dist/haClient/updates.js +48 -0
  54. package/dist/haClient/updates.js.map +1 -0
  55. package/dist/haClient.d.ts +97 -0
  56. package/dist/haClient.d.ts.map +1 -0
  57. package/dist/haClient.js +344 -0
  58. package/dist/haClient.js.map +1 -0
  59. package/dist/index.d.ts +8 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +92 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/localAI/index.d.ts +37 -0
  64. package/dist/localAI/index.d.ts.map +1 -0
  65. package/dist/localAI/index.js +73 -0
  66. package/dist/localAI/index.js.map +1 -0
  67. package/dist/localAI/prompts.d.ts +20 -0
  68. package/dist/localAI/prompts.d.ts.map +1 -0
  69. package/dist/localAI/prompts.js +48 -0
  70. package/dist/localAI/prompts.js.map +1 -0
  71. package/dist/localAI/providers/base.d.ts +51 -0
  72. package/dist/localAI/providers/base.d.ts.map +1 -0
  73. package/dist/localAI/providers/base.js +80 -0
  74. package/dist/localAI/providers/base.js.map +1 -0
  75. package/dist/localAI/providers/ollama.d.ts +22 -0
  76. package/dist/localAI/providers/ollama.d.ts.map +1 -0
  77. package/dist/localAI/providers/ollama.js +63 -0
  78. package/dist/localAI/providers/ollama.js.map +1 -0
  79. package/dist/localAI/providers/openai.d.ts +23 -0
  80. package/dist/localAI/providers/openai.d.ts.map +1 -0
  81. package/dist/localAI/providers/openai.js +63 -0
  82. package/dist/localAI/providers/openai.js.map +1 -0
  83. package/dist/localAI/types.d.ts +61 -0
  84. package/dist/localAI/types.d.ts.map +1 -0
  85. package/dist/localAI/types.js +6 -0
  86. package/dist/localAI/types.js.map +1 -0
  87. package/dist/ollamaClient.d.ts +37 -0
  88. package/dist/ollamaClient.d.ts.map +1 -0
  89. package/dist/ollamaClient.js +110 -0
  90. package/dist/ollamaClient.js.map +1 -0
  91. package/dist/resources/index.d.ts +11 -0
  92. package/dist/resources/index.d.ts.map +1 -0
  93. package/dist/resources/index.js +238 -0
  94. package/dist/resources/index.js.map +1 -0
  95. package/dist/server/common.d.ts +9 -0
  96. package/dist/server/common.d.ts.map +1 -0
  97. package/dist/server/common.js +27 -0
  98. package/dist/server/common.js.map +1 -0
  99. package/dist/server/eventSubscription.d.ts +53 -0
  100. package/dist/server/eventSubscription.d.ts.map +1 -0
  101. package/dist/server/eventSubscription.js +257 -0
  102. package/dist/server/eventSubscription.js.map +1 -0
  103. package/dist/server/http.d.ts +12 -0
  104. package/dist/server/http.d.ts.map +1 -0
  105. package/dist/server/http.js +491 -0
  106. package/dist/server/http.js.map +1 -0
  107. package/dist/server/rateLimiter.d.ts +70 -0
  108. package/dist/server/rateLimiter.d.ts.map +1 -0
  109. package/dist/server/rateLimiter.js +156 -0
  110. package/dist/server/rateLimiter.js.map +1 -0
  111. package/dist/server/sse.d.ts +32 -0
  112. package/dist/server/sse.d.ts.map +1 -0
  113. package/dist/server/sse.js +76 -0
  114. package/dist/server/sse.js.map +1 -0
  115. package/dist/server/stdio.d.ts +8 -0
  116. package/dist/server/stdio.d.ts.map +1 -0
  117. package/dist/server/stdio.js +16 -0
  118. package/dist/server/stdio.js.map +1 -0
  119. package/dist/server/stream.d.ts +26 -0
  120. package/dist/server/stream.d.ts.map +1 -0
  121. package/dist/server/stream.js +81 -0
  122. package/dist/server/stream.js.map +1 -0
  123. package/dist/server.d.ts +8 -0
  124. package/dist/server.d.ts.map +1 -0
  125. package/dist/server.js +30 -0
  126. package/dist/server.js.map +1 -0
  127. package/dist/tools/callService.d.ts +8 -0
  128. package/dist/tools/callService.d.ts.map +1 -0
  129. package/dist/tools/callService.js +100 -0
  130. package/dist/tools/callService.js.map +1 -0
  131. package/dist/tools/getClient.d.ts +8 -0
  132. package/dist/tools/getClient.d.ts.map +1 -0
  133. package/dist/tools/getClient.js +94 -0
  134. package/dist/tools/getClient.js.map +1 -0
  135. package/dist/tools/getDevice.d.ts +8 -0
  136. package/dist/tools/getDevice.d.ts.map +1 -0
  137. package/dist/tools/getDevice.js +94 -0
  138. package/dist/tools/getDevice.js.map +1 -0
  139. package/dist/tools/getEntitiesByDomain.d.ts +8 -0
  140. package/dist/tools/getEntitiesByDomain.d.ts.map +1 -0
  141. package/dist/tools/getEntitiesByDomain.js +80 -0
  142. package/dist/tools/getEntitiesByDomain.js.map +1 -0
  143. package/dist/tools/getState.d.ts +8 -0
  144. package/dist/tools/getState.d.ts.map +1 -0
  145. package/dist/tools/getState.js +90 -0
  146. package/dist/tools/getState.js.map +1 -0
  147. package/dist/tools/getStates.d.ts +8 -0
  148. package/dist/tools/getStates.d.ts.map +1 -0
  149. package/dist/tools/getStates.js +70 -0
  150. package/dist/tools/getStates.js.map +1 -0
  151. package/dist/tools/index.d.ts +9 -0
  152. package/dist/tools/index.d.ts.map +1 -0
  153. package/dist/tools/index.js +9 -0
  154. package/dist/tools/index.js.map +1 -0
  155. package/dist/tools/listClients.d.ts +8 -0
  156. package/dist/tools/listClients.d.ts.map +1 -0
  157. package/dist/tools/listClients.js +73 -0
  158. package/dist/tools/listClients.js.map +1 -0
  159. package/dist/tools/listDevices.d.ts +8 -0
  160. package/dist/tools/listDevices.d.ts.map +1 -0
  161. package/dist/tools/listDevices.js +73 -0
  162. package/dist/tools/listDevices.js.map +1 -0
  163. package/dist/tools/listSites.d.ts +8 -0
  164. package/dist/tools/listSites.d.ts.map +1 -0
  165. package/dist/tools/listSites.js +67 -0
  166. package/dist/tools/listSites.js.map +1 -0
  167. package/dist/tools/registry.d.ts +9 -0
  168. package/dist/tools/registry.d.ts.map +1 -0
  169. package/dist/tools/registry.js +1382 -0
  170. package/dist/tools/registry.js.map +1 -0
  171. package/dist/tools/searchDevices.d.ts +8 -0
  172. package/dist/tools/searchDevices.d.ts.map +1 -0
  173. package/dist/tools/searchDevices.js +84 -0
  174. package/dist/tools/searchDevices.js.map +1 -0
  175. package/dist/tools/searchEntities.d.ts +8 -0
  176. package/dist/tools/searchEntities.d.ts.map +1 -0
  177. package/dist/tools/searchEntities.js +80 -0
  178. package/dist/tools/searchEntities.js.map +1 -0
  179. package/dist/types/entity.d.ts +98 -0
  180. package/dist/types/entity.d.ts.map +1 -0
  181. package/dist/types/entity.js +2 -0
  182. package/dist/types/entity.js.map +1 -0
  183. package/dist/types/errors.d.ts +16 -0
  184. package/dist/types/errors.d.ts.map +1 -0
  185. package/dist/types/errors.js +32 -0
  186. package/dist/types/errors.js.map +1 -0
  187. package/dist/types/haConfig.d.ts +8 -0
  188. package/dist/types/haConfig.d.ts.map +1 -0
  189. package/dist/types/haConfig.js +2 -0
  190. package/dist/types/haConfig.js.map +1 -0
  191. package/dist/types/index.d.ts +6 -0
  192. package/dist/types/index.d.ts.map +1 -0
  193. package/dist/types/index.js +2 -0
  194. package/dist/types/index.js.map +1 -0
  195. package/dist/types/request.d.ts +7 -0
  196. package/dist/types/request.d.ts.map +1 -0
  197. package/dist/types/request.js +2 -0
  198. package/dist/types/request.js.map +1 -0
  199. package/dist/types/serviceCall.d.ts +18 -0
  200. package/dist/types/serviceCall.d.ts.map +1 -0
  201. package/dist/types/serviceCall.js +2 -0
  202. package/dist/types/serviceCall.js.map +1 -0
  203. package/dist/types.d.ts +2 -0
  204. package/dist/types.d.ts.map +1 -0
  205. package/dist/types.js +3 -0
  206. package/dist/types.js.map +1 -0
  207. package/dist/utils/config-validations.d.ts +12 -0
  208. package/dist/utils/config-validations.d.ts.map +1 -0
  209. package/dist/utils/config-validations.js +43 -0
  210. package/dist/utils/config-validations.js.map +1 -0
  211. package/dist/utils/logger.d.ts +24 -0
  212. package/dist/utils/logger.d.ts.map +1 -0
  213. package/dist/utils/logger.js +94 -0
  214. package/dist/utils/logger.js.map +1 -0
  215. package/dist/wrapper.d.ts +8 -0
  216. package/dist/wrapper.d.ts.map +1 -0
  217. package/dist/wrapper.js +301 -0
  218. package/dist/wrapper.js.map +1 -0
  219. package/docker-compose.yml +17 -0
  220. package/docs/CHANGELOG.md +86 -0
  221. package/docs/LOCAL_CLIENT.md +268 -0
  222. package/docs/SSE_API.md +341 -0
  223. package/eslint.config.js +33 -0
  224. package/n8n/ai-agent-safety-monitor.json +355 -0
  225. package/openapi.json +166 -0
  226. package/package.json +51 -0
  227. package/src/config.ts +260 -0
  228. package/src/haClient/automations.ts +217 -0
  229. package/src/haClient/config.ts +41 -0
  230. package/src/haClient/devices.ts +653 -0
  231. package/src/haClient/entities.ts +128 -0
  232. package/src/haClient/events.ts +456 -0
  233. package/src/haClient/history.ts +68 -0
  234. package/src/haClient/index.ts +287 -0
  235. package/src/haClient/request.ts +127 -0
  236. package/src/haClient/services.ts +45 -0
  237. package/src/haClient/states.ts +60 -0
  238. package/src/haClient/updates.ts +58 -0
  239. package/src/index.ts +99 -0
  240. package/src/localAI/index.ts +88 -0
  241. package/src/localAI/prompts.ts +50 -0
  242. package/src/localAI/providers/base.ts +120 -0
  243. package/src/localAI/providers/ollama.ts +78 -0
  244. package/src/localAI/providers/openai.ts +86 -0
  245. package/src/localAI/types.ts +67 -0
  246. package/src/resources/index.ts +266 -0
  247. package/src/server/common.ts +38 -0
  248. package/src/server/eventSubscription.ts +318 -0
  249. package/src/server/http.ts +546 -0
  250. package/src/server/rateLimiter.ts +195 -0
  251. package/src/server/sse.ts +120 -0
  252. package/src/server/stdio.ts +22 -0
  253. package/src/server/stream.ts +113 -0
  254. package/src/tools/callService.ts +114 -0
  255. package/src/tools/getEntitiesByDomain.ts +92 -0
  256. package/src/tools/getState.ts +103 -0
  257. package/src/tools/getStates.ts +80 -0
  258. package/src/tools/index.ts +9 -0
  259. package/src/tools/registry.ts +1521 -0
  260. package/src/tools/searchEntities.ts +92 -0
  261. package/src/types/entity.ts +110 -0
  262. package/src/types/errors.ts +33 -0
  263. package/src/types/haConfig.ts +7 -0
  264. package/src/types/index.ts +5 -0
  265. package/src/types/request.ts +6 -0
  266. package/src/types/serviceCall.ts +18 -0
  267. package/src/types.ts +2 -0
  268. package/src/utils/config-validations.ts +46 -0
  269. package/src/utils/logger.ts +114 -0
  270. package/src/wrapper.ts +340 -0
  271. package/tests/config.test.ts +332 -0
  272. package/tests/haClient/automations.test.ts +133 -0
  273. package/tests/haClient/config.test.ts +96 -0
  274. package/tests/haClient/entities.test.ts +188 -0
  275. package/tests/haClient/history.test.ts +170 -0
  276. package/tests/haClient/index.test.ts +221 -0
  277. package/tests/haClient/request.test.ts +217 -0
  278. package/tests/haClient/services.test.ts +120 -0
  279. package/tests/haClient/states.test.ts +119 -0
  280. package/tests/haClient/updates.test.ts +193 -0
  281. package/tests/index.test.ts +164 -0
  282. package/tests/localAI/index.test.ts +168 -0
  283. package/tests/localAI/ollama.test.ts +187 -0
  284. package/tests/localAI/openai.test.ts +257 -0
  285. package/tests/server/common.test.ts +64 -0
  286. package/tests/server/http.test.ts +326 -0
  287. package/tests/server/sse.test.ts +240 -0
  288. package/tests/server/stdio.test.ts +102 -0
  289. package/tests/server/stream.test.ts +287 -0
  290. package/tests/tools/registry.test.ts +355 -0
  291. package/tests/utils/config-validations.test.ts +289 -0
  292. package/tests/utils/logger.test.ts +327 -0
  293. package/tsconfig.json +25 -0
  294. 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,7 @@
1
+ export interface HaConfig {
2
+ baseUrl: string;
3
+ token: string;
4
+ strictSsl: boolean;
5
+ timeout: number;
6
+ logLevel: 'debug' | 'info' | 'warn' | 'error';
7
+ }
@@ -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,6 @@
1
+ export interface RequestOptions {
2
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
3
+ body?: unknown;
4
+ headers?: Record<string, string>;
5
+ params?: Record<string, string | number | boolean>;
6
+ }
@@ -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,2 @@
1
+ // Barrel re-export from types folder
2
+ export * from './types/index.js';
@@ -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 };