initial commit

This commit is contained in:
Tevon Strand-Brown
2024-12-11 22:31:43 -08:00
parent 140576a9a5
commit 56c34635be
8 changed files with 3733 additions and 0 deletions

36
src/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# Dependencies
node_modules/
yarn.lock
# Build output
dist/
build/
*.tsbuildinfo
# Environment variables
.env
.env.local
.env.*.local
# IDE/Editor
.vscode/
.idea/
*.swp
*.swo
# Logs
logs/
*.log
npm-debug.log*
# Testing
coverage/
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

27
src/hass/index.ts Normal file
View File

@@ -0,0 +1,27 @@
import { CreateApplication, TServiceParams, StringConfig } from "@digital-alchemy/core";
type Environments = "development" | "production" | "test";
import { LIB_HASS } from "@digital-alchemy/hass";
// application
const MY_APP = CreateApplication({
configuration: {
NODE_ENV: {
type: "string",
default: "development",
enum: ["development", "production", "test"],
description: "Code runner addon can set with it's own NODE_ENV",
} satisfies StringConfig<Environments>,
},
services: {},
libraries: [LIB_HASS],
name: 'boilerplate'
});
const hass = await MY_APP.bootstrap()
export async function get_hass() {
return hass;
}

131
src/index.ts Normal file
View File

@@ -0,0 +1,131 @@
import { get_hass } from "./hass/index.js";
import { LiteMCP } from "litemcp";
import { z } from "zod";
import { TAreaId, TFloorId, TRawDomains, TRawEntityIds } from "@digital-alchemy/hass";
import { zodToJsonSchema } from "zod-to-json-schema";
import { ListRequestSchema, AreaSchema, FloorSchema } from "./schemas.js";
const server = new LiteMCP(
"example-server",
"1.0.0",
);
const hass = await get_hass();
server.addTool({
name: "list_domains",
description: "Lists all domains in the home",
parameters: z.object({}),
execute: async () => {
return ["light", "climate", "alarm_control_panel", "cover", "switch", "sensor", "button"];
}
});
server.addTool({
name: "list_areas",
description: "Lists all areas in the home",
parameters: z.object({}),
execute: async () => {
return await areasRequestHandler();
}
});
server.addTool({
name: "list_floors",
description: "Lists all floors in the home",
parameters: z.object({}),
execute: async () => {
return await floorsRequestHandler();
}
});
server.addTool({
name: "get_entity_state",
description: "Gets the state of an entity",
parameters: z.object({
entity_id: z.string()
}),
execute: async (request) => {
return await hass.hass.entity.getCurrentState(request.entity_id as TRawEntityIds);
}
});
server.addTool({
name: "get_entities",
description: "Gets entities, filtered by domain, floor, and area as needed",
parameters: z.object({
domain: z.string().optional(),
floor: z.string().optional(),
area: z.string().optional(),
}),
execute: async (request) => {
if (request.floor) {
return hass.hass.idBy.floor(request.floor as TFloorId, request.domain as TRawDomains || undefined);
}
if (request.area) {
return hass.hass.idBy.area(request.area as TAreaId, request.domain as TRawDomains || undefined);
}
if (request.domain) {
return hass.hass.entity.listEntities(request.domain as TRawDomains);
}
return hass.hass.entity.listEntities();
}
});
server.addTool({
name: "get_entity_state_by_ids",
description: "Gets a list of entities from a list of entity ids. Use this tool when there is more than one entity to get the state of.",
parameters: z.object({
entity_ids: z.array(z.string())
}),
execute: async (request) => {
const entities = request.entity_ids.map(entity_id => hass.hass.entity.getCurrentState(entity_id as TRawEntityIds));
return entities;
}
})
server.addTool({
name: "get_entity_history",
description: "Gets the history of an entity",
parameters: z.object({
entity_id: z.string(),
start_time: z.string(),
end_time: z.string().optional()
}),
execute: async (request) => {
return await hass.hass.entity.history({
end_time: request.end_time ? new Date(request.end_time) : new Date(),
entity_ids: [request.entity_id as TRawEntityIds],
start_time: request.start_time
});
}
})
server.addTool({
name: "get_entity_history_by_ids",
description: "Gets the history of a list of entities",
parameters: z.object({
entity_ids: z.array(z.string()),
start_time: z.string(),
end_time: z.string().optional()
}),
execute: async (request) => {
return await hass.hass.entity.history({
entity_ids: request.entity_ids as TRawEntityIds[],
end_time: request.end_time ? new Date(request.end_time) : new Date(),
start_time: request.start_time
});
}
})
const areasRequestHandler = async () => {
const areas = await hass.hass.area.list()
return areas;
}
const floorsRequestHandler = async () => {
const floors = await hass.hass.floor.list()
return floors;
}
server.start();

82
src/schemas.ts Normal file
View File

@@ -0,0 +1,82 @@
import { z } from "zod";
export const DomainSchema = z.enum(["light", "climate", "alarm_control_panel", "cover", "switch"]);
// Generic list request schema
export const ListRequestSchema = z.object({
domain: DomainSchema,
area: z.string().optional(),
floor: z.string().optional(),
});
// Areas
export const AreaSchema = z.object({
id: z.string(),
name: z.string(),
floor: z.string(),
});
export const FloorSchema = z.object({
id: z.string(),
name: z.string(),
});
export const ListFloorsResponseSchema = z.object({
floors: z.array(FloorSchema),
});
// Alarm
export const AlarmAttributesSchema = z.object({
code_format: z.string().optional(),
changed_by: z.string().optional(),
code_arm_required: z.boolean().optional(),
friendly_name: z.string().optional(),
supported_features: z.number().optional(),
});
export const AlarmSchema = z.object({
entity_id: z.string(),
state: z.string(),
state_attributes: AlarmAttributesSchema,
});
export const ListAlarmsResponseSchema = z.object({
alarms: z.array(AlarmSchema),
});
// Devices
export const DeviceSchema = z.object({
id: z.string(),
name: z.string(),
name_by_user: z.string().optional(),
model: z.string(),
model_id: z.string().nullable(),
manufacturer: z.string(),
area_id: z.string().nullable(),
config_entries: z.array(z.string()),
primary_config_entry: z.string(),
connections: z.array(z.tuple([z.string(), z.string()])),
configuration_url: z.string().nullable(),
disabled_by: z.string().nullable(),
entry_type: z.string().nullable(),
hw_version: z.string().nullable(),
sw_version: z.string().nullable(),
via_device_id: z.string().nullable(),
created_at: z.number(),
modified_at: z.number(),
identifiers: z.array(z.any()),
labels: z.array(z.string()),
serial_number: z.string().optional()
});
export const ListDevicesResponseSchema = z.object({
_meta: z.object({}).optional(),
devices: z.array(DeviceSchema)
});