Initial commit: Home Assistant MCP Server
This commit is contained in:
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Home Assistant Configuration
|
||||||
|
HA_URL=http://localhost:8123
|
||||||
|
HA_TOKEN=your_long_lived_access_token_here
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
98
README.md
Normal file
98
README.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Home Assistant MCP Server
|
||||||
|
|
||||||
|
An MCP (Model Context Protocol) server that enables LLMs to interact with Home Assistant through its REST API.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **get_states** - Get entity states (all or specific)
|
||||||
|
- **call_service** - Call Home Assistant services (turn on/off lights, switches, etc.)
|
||||||
|
- **get_history** - Get historical state data for entities
|
||||||
|
- **get_config** - Get Home Assistant configuration
|
||||||
|
- **get_services** - List available services
|
||||||
|
- **render_template** - Render Jinja2 templates
|
||||||
|
- **get_logbook** - Get logbook entries
|
||||||
|
- **fire_event** - Fire custom events
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Generate a Long-Lived Access Token
|
||||||
|
|
||||||
|
1. Log in to your Home Assistant instance
|
||||||
|
2. Go to your Profile (click on your name in the sidebar)
|
||||||
|
3. Scroll down to "Long-Lived Access Tokens"
|
||||||
|
4. Click "Create Token"
|
||||||
|
5. Give it a name (e.g., "MCP Server")
|
||||||
|
6. Copy the token immediately (it won't be shown again!)
|
||||||
|
|
||||||
|
### 2. Configure Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env` with your Home Assistant URL and token:
|
||||||
|
|
||||||
|
```
|
||||||
|
HA_URL=http://your-home-assistant-ip:8123
|
||||||
|
HA_TOKEN=your_long_lived_access_token
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Install and Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### With Claude Desktop
|
||||||
|
|
||||||
|
Add to your Claude Desktop configuration (`~/.config/Claude/claude_desktop_config.json` on Linux):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"home-assistant": {
|
||||||
|
"command": "node",
|
||||||
|
"args": ["/path/to/home-assistant-mcp/dist/index.js"],
|
||||||
|
"env": {
|
||||||
|
"HA_URL": "http://your-home-assistant-ip:8123",
|
||||||
|
"HA_TOKEN": "your_long_lived_access_token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Other MCP Clients
|
||||||
|
|
||||||
|
Run the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
The server communicates via stdio using the MCP protocol.
|
||||||
|
|
||||||
|
## Example Commands
|
||||||
|
|
||||||
|
Once connected, you can ask the LLM things like:
|
||||||
|
|
||||||
|
- "What's the current temperature in the living room?"
|
||||||
|
- "Turn on the kitchen lights"
|
||||||
|
- "Show me what happened in the last hour"
|
||||||
|
- "What's the status of all my lights?"
|
||||||
|
- "Set the thermostat to 22 degrees"
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Watch mode for TypeScript:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
1178
package-lock.json
generated
Normal file
1178
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "home-assistant-mcp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "MCP Server for Home Assistant integration",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"bin": {
|
||||||
|
"home-assistant-mcp": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"dev": "tsc --watch"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"mcp",
|
||||||
|
"home-assistant",
|
||||||
|
"llm",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||||
|
"dotenv": "^16.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.10.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
run_server.sh
Executable file
4
run_server.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export HA_URL="http://10.8.0.17:8123"
|
||||||
|
export HA_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3MjkxZDFhNmQ1ZDQ0MGI1YmQ5ODgwYTZlZTZjYmIxYiIsImlhdCI6MTc2OTkxNzcyNSwiZXhwIjoyMDg1Mjc3NzI1fQ.Bpqs8Tr7Nxle377HbIP5u8fQZnSL5rQjYxBYaeEJMuw"
|
||||||
|
exec /usr/bin/node /home/wartana/myApp/home-assistant-mcp/dist/index.js
|
||||||
220
src/client.ts
Normal file
220
src/client.ts
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
/**
|
||||||
|
* Home Assistant API Client
|
||||||
|
* Provides HTTP client for interacting with Home Assistant REST API
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface HAState {
|
||||||
|
entity_id: string;
|
||||||
|
state: string;
|
||||||
|
attributes: Record<string, unknown>;
|
||||||
|
last_changed: string;
|
||||||
|
last_updated: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HAConfig {
|
||||||
|
components: string[];
|
||||||
|
config_dir: string;
|
||||||
|
elevation: number;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
location_name: string;
|
||||||
|
time_zone: string;
|
||||||
|
unit_system: {
|
||||||
|
length: string;
|
||||||
|
mass: string;
|
||||||
|
temperature: string;
|
||||||
|
volume: string;
|
||||||
|
};
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HAService {
|
||||||
|
domain: string;
|
||||||
|
services: Record<string, {
|
||||||
|
description?: string;
|
||||||
|
fields?: Record<string, unknown>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HAHistoryEntry {
|
||||||
|
entity_id: string;
|
||||||
|
state: string;
|
||||||
|
attributes?: Record<string, unknown>;
|
||||||
|
last_changed: string;
|
||||||
|
last_updated?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HALogbookEntry {
|
||||||
|
context_user_id: string | null;
|
||||||
|
domain: string;
|
||||||
|
entity_id: string;
|
||||||
|
message: string;
|
||||||
|
name: string;
|
||||||
|
when: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HomeAssistantClient {
|
||||||
|
private baseUrl: string;
|
||||||
|
private token: string;
|
||||||
|
|
||||||
|
constructor(baseUrl: string, token: string) {
|
||||||
|
// Remove trailing slash if present
|
||||||
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async request<T>(
|
||||||
|
endpoint: string,
|
||||||
|
options: RequestInit = {}
|
||||||
|
): Promise<T> {
|
||||||
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${this.token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Home Assistant API error (${response.status}): ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some endpoints return plain text
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (contentType?.includes('application/json')) {
|
||||||
|
return response.json() as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.text() as unknown as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the API is running
|
||||||
|
*/
|
||||||
|
async checkApi(): Promise<{ message: string }> {
|
||||||
|
return this.request<{ message: string }>('/api/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Home Assistant configuration
|
||||||
|
*/
|
||||||
|
async getConfig(): Promise<HAConfig> {
|
||||||
|
return this.request<HAConfig>('/api/config');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all entity states
|
||||||
|
*/
|
||||||
|
async getStates(): Promise<HAState[]> {
|
||||||
|
return this.request<HAState[]>('/api/states');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get state of a specific entity
|
||||||
|
*/
|
||||||
|
async getState(entityId: string): Promise<HAState> {
|
||||||
|
return this.request<HAState>(`/api/states/${entityId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available services
|
||||||
|
*/
|
||||||
|
async getServices(): Promise<HAService[]> {
|
||||||
|
return this.request<HAService[]>('/api/services');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a service
|
||||||
|
*/
|
||||||
|
async callService(
|
||||||
|
domain: string,
|
||||||
|
service: string,
|
||||||
|
data?: Record<string, unknown>,
|
||||||
|
returnResponse = false
|
||||||
|
): Promise<HAState[] | Record<string, unknown>> {
|
||||||
|
const endpoint = `/api/services/${domain}/${service}${returnResponse ? '?return_response' : ''}`;
|
||||||
|
return this.request<HAState[] | Record<string, unknown>>(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get entity history
|
||||||
|
*/
|
||||||
|
async getHistory(
|
||||||
|
entityId: string,
|
||||||
|
startTime?: string,
|
||||||
|
endTime?: string,
|
||||||
|
minimalResponse = true
|
||||||
|
): Promise<HAHistoryEntry[][]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set('filter_entity_id', entityId);
|
||||||
|
if (minimalResponse) {
|
||||||
|
params.set('minimal_response', '');
|
||||||
|
}
|
||||||
|
if (endTime) {
|
||||||
|
params.set('end_time', endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = startTime || '';
|
||||||
|
const endpoint = `/api/history/period/${timestamp}?${params.toString()}`;
|
||||||
|
return this.request<HAHistoryEntry[][]>(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get logbook entries
|
||||||
|
*/
|
||||||
|
async getLogbook(
|
||||||
|
entityId?: string,
|
||||||
|
startTime?: string,
|
||||||
|
endTime?: string
|
||||||
|
): Promise<HALogbookEntry[]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (entityId) {
|
||||||
|
params.set('entity', entityId);
|
||||||
|
}
|
||||||
|
if (endTime) {
|
||||||
|
params.set('end_time', endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = startTime || '';
|
||||||
|
const queryString = params.toString();
|
||||||
|
const endpoint = `/api/logbook/${timestamp}${queryString ? '?' + queryString : ''}`;
|
||||||
|
return this.request<HALogbookEntry[]>(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a template
|
||||||
|
*/
|
||||||
|
async renderTemplate(template: string): Promise<string> {
|
||||||
|
return this.request<string>('/api/template', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ template }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get error log
|
||||||
|
*/
|
||||||
|
async getErrorLog(): Promise<string> {
|
||||||
|
return this.request<string>('/api/error_log');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire an event
|
||||||
|
*/
|
||||||
|
async fireEvent(
|
||||||
|
eventType: string,
|
||||||
|
eventData?: Record<string, unknown>
|
||||||
|
): Promise<{ message: string }> {
|
||||||
|
return this.request<{ message: string }>(`/api/events/${eventType}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(eventData || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
356
src/index.ts
Normal file
356
src/index.ts
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Home Assistant MCP Server
|
||||||
|
* Provides tools for LLMs to interact with Home Assistant
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||||
|
import {
|
||||||
|
CallToolRequestSchema,
|
||||||
|
ListToolsRequestSchema,
|
||||||
|
Tool,
|
||||||
|
} from '@modelcontextprotocol/sdk/types.js';
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
import { HomeAssistantClient } from './client.js';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
config();
|
||||||
|
|
||||||
|
const HA_URL = process.env.HA_URL || 'http://localhost:8123';
|
||||||
|
const HA_TOKEN = process.env.HA_TOKEN || '';
|
||||||
|
|
||||||
|
if (!HA_TOKEN) {
|
||||||
|
console.error('Error: HA_TOKEN environment variable is required');
|
||||||
|
console.error('Please set HA_TOKEN to your Home Assistant Long-Lived Access Token');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Home Assistant client
|
||||||
|
const haClient = new HomeAssistantClient(HA_URL, HA_TOKEN);
|
||||||
|
|
||||||
|
// Define available tools
|
||||||
|
const tools: Tool[] = [
|
||||||
|
{
|
||||||
|
name: 'get_states',
|
||||||
|
description: 'Get the current state of entities in Home Assistant. Returns all entities if no entity_id is specified, or a specific entity if entity_id is provided.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
entity_id: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional. The entity ID to get state for (e.g., "light.living_room", "sensor.temperature"). If not provided, returns all entities.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'call_service',
|
||||||
|
description: 'Call a Home Assistant service to control devices. Common services include: light.turn_on, light.turn_off, switch.turn_on, switch.turn_off, climate.set_temperature, etc.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
domain: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The service domain (e.g., "light", "switch", "climate", "automation")',
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The service to call (e.g., "turn_on", "turn_off", "toggle", "set_temperature")',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Optional service data. Usually includes "entity_id" and service-specific parameters.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['domain', 'service'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_history',
|
||||||
|
description: 'Get the state history of an entity over a time period. Useful for checking historical values of sensors, when devices were turned on/off, etc.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
entity_id: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The entity ID to get history for (e.g., "sensor.temperature")',
|
||||||
|
},
|
||||||
|
start_time: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional. Start time in ISO format (e.g., "2024-01-01T00:00:00"). Defaults to 24 hours ago.',
|
||||||
|
},
|
||||||
|
end_time: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional. End time in ISO format. Defaults to now.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['entity_id'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_config',
|
||||||
|
description: 'Get Home Assistant configuration including version, location, timezone, and installed components.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_services',
|
||||||
|
description: 'Get a list of all available services in Home Assistant. Optionally filter by domain.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
domain: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional. Filter services by domain (e.g., "light", "switch")',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'render_template',
|
||||||
|
description: 'Render a Home Assistant Jinja2 template. Useful for getting computed values or formatted information.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The Jinja2 template to render. Example: "{{ states(\'sensor.temperature\') }} °C"',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['template'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_logbook',
|
||||||
|
description: 'Get logbook entries showing what happened in Home Assistant. Shows events like state changes, automations triggered, etc.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
entity_id: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional. Filter logbook entries by entity ID.',
|
||||||
|
},
|
||||||
|
start_time: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional. Start time in ISO format. Defaults to 24 hours ago.',
|
||||||
|
},
|
||||||
|
end_time: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional. End time in ISO format. Defaults to now.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fire_event',
|
||||||
|
description: 'Fire a custom event in Home Assistant. Useful for triggering automations or custom integrations.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
event_type: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The event type to fire (e.g., "custom_event", "my_automation_trigger")',
|
||||||
|
},
|
||||||
|
event_data: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Optional. Data to include with the event.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['event_type'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create server
|
||||||
|
const server = new Server(
|
||||||
|
{
|
||||||
|
name: 'home-assistant-mcp',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle list tools request
|
||||||
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
|
return { tools };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle tool calls
|
||||||
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
case 'get_states': {
|
||||||
|
const entityId = args?.entity_id as string | undefined;
|
||||||
|
if (entityId) {
|
||||||
|
const state = await haClient.getState(entityId);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify(state, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const states = await haClient.getStates();
|
||||||
|
// Return summary for all states to avoid overwhelming response
|
||||||
|
const summary = states.map((s) => ({
|
||||||
|
entity_id: s.entity_id,
|
||||||
|
state: s.state,
|
||||||
|
friendly_name: s.attributes.friendly_name,
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify(summary, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'call_service': {
|
||||||
|
const domain = args?.domain as string;
|
||||||
|
const service = args?.service as string;
|
||||||
|
const data = args?.data as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
|
const result = await haClient.callService(domain, service, data);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `Service ${domain}.${service} called successfully.\n\nChanged states:\n${JSON.stringify(result, null, 2)}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_history': {
|
||||||
|
const entityId = args?.entity_id as string;
|
||||||
|
const startTime = args?.start_time as string | undefined;
|
||||||
|
const endTime = args?.end_time as string | undefined;
|
||||||
|
|
||||||
|
const history = await haClient.getHistory(entityId, startTime, endTime);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify(history, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_config': {
|
||||||
|
const config = await haClient.getConfig();
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify(config, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_services': {
|
||||||
|
const domain = args?.domain as string | undefined;
|
||||||
|
let services = await haClient.getServices();
|
||||||
|
|
||||||
|
if (domain) {
|
||||||
|
services = services.filter((s) => s.domain === domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify(services, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'render_template': {
|
||||||
|
const template = args?.template as string;
|
||||||
|
const result = await haClient.renderTemplate(template);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: result,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_logbook': {
|
||||||
|
const entityId = args?.entity_id as string | undefined;
|
||||||
|
const startTime = args?.start_time as string | undefined;
|
||||||
|
const endTime = args?.end_time as string | undefined;
|
||||||
|
|
||||||
|
const logbook = await haClient.getLogbook(entityId, startTime, endTime);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify(logbook, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'fire_event': {
|
||||||
|
const eventType = args?.event_type as string;
|
||||||
|
const eventData = args?.event_data as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
|
const result = await haClient.fireEvent(eventType, eventData);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: result.message,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${name}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `Error: ${errorMessage}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
async function main() {
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error('Home Assistant MCP Server running on stdio');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('Fatal error:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": [
|
||||||
|
"ES2022"
|
||||||
|
],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user