Add Docker support and enhance configuration management
- Introduced Dockerfile for building and running the application in a containerized environment. - Added .dockerignore to exclude unnecessary files from the Docker context. - Updated README.md with detailed Docker installation instructions and Node.js version management using nvm. - Refactored environment variable handling in src/index.ts and src/config/hass.config.ts for improved configuration management. - Enhanced TypeScript configuration to include JSON module resolution and updated exclusion patterns. - Updated .gitignore to include additional files for better environment management.
This commit is contained in:
30
.dockerignore
Normal file
30
.dockerignore
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Environment and config
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
__tests__
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
CHANGELOG.md
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -20,7 +20,7 @@ wheels/
|
|||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
.cursorrules
|
||||||
# Environment variables
|
# Environment variables
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
38
Dockerfile
Normal file
38
Dockerfile
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM node:20.10.0-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install TypeScript globally
|
||||||
|
RUN npm install -g typescript
|
||||||
|
|
||||||
|
# Copy source files first
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Install all dependencies (including dev dependencies)
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Build the project
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:20.10.0-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Set Node options for better compatibility
|
||||||
|
ENV NODE_OPTIONS="--experimental-modules"
|
||||||
|
ENV NODE_ENV="production"
|
||||||
|
|
||||||
|
# Copy package files and install production dependencies
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install --omit=dev --ignore-scripts
|
||||||
|
|
||||||
|
# Copy built files from builder stage
|
||||||
|
COPY --from=builder /app/dist ./dist
|
||||||
|
|
||||||
|
# Expose default port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
98
README.md
98
README.md
@@ -16,23 +16,99 @@ The server uses the MCP protocol to share access to a local Home Assistant insta
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Node.js 20.10.0 or higher
|
- Node.js 20.10.0 or higher (Required for Array.prototype.toSorted())
|
||||||
- NPM package manager
|
- NPM package manager
|
||||||
- A running Home Assistant instance
|
- A running Home Assistant instance
|
||||||
- A long-lived access token from Home Assistant
|
- A long-lived access token from Home Assistant
|
||||||
|
|
||||||
|
### Node.js Version Management
|
||||||
|
|
||||||
|
If you're using an older version of Node.js, you can use `nvm` (Node Version Manager) to install and use the correct version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install nvm (if not already installed)
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||||
|
|
||||||
|
# Reload shell configuration
|
||||||
|
source ~/.bashrc # or source ~/.zshrc for Zsh
|
||||||
|
|
||||||
|
# Install Node.js 20.10.0
|
||||||
|
nvm install 20.10.0
|
||||||
|
|
||||||
|
# Use Node.js 20.10.0
|
||||||
|
nvm use 20.10.0
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Classic Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the repository
|
# Clone the repository
|
||||||
git clone https://github.com/jango-blockchained/homeassistant-mcp.git
|
git clone https://github.com/jango-blockchained/homeassistant-mcp.git
|
||||||
cd homeassistant-mcp
|
cd homeassistant-mcp
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
yarn install
|
npm install
|
||||||
|
|
||||||
# Build the server
|
# Build the server
|
||||||
yarn build
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Installation
|
||||||
|
|
||||||
|
#### Using Docker Compose (Recommended)
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/jango-blockchained/homeassistant-mcp.git
|
||||||
|
cd homeassistant-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a `.env` file with your Home Assistant configuration:
|
||||||
|
```env
|
||||||
|
NODE_ENV=production
|
||||||
|
HASS_HOST=your_home_assistant_url
|
||||||
|
HASS_TOKEN=your_home_assistant_token
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the container:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Docker Directly
|
||||||
|
|
||||||
|
1. Build the image:
|
||||||
|
```bash
|
||||||
|
docker build -t homeassistant-mcp .
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the container:
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name homeassistant-mcp \
|
||||||
|
-e HASS_HOST=your_home_assistant_url \
|
||||||
|
-e HASS_TOKEN=your_home_assistant_token \
|
||||||
|
-p 3000:3000 \
|
||||||
|
homeassistant-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Management Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop the container
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Restart the container
|
||||||
|
docker-compose restart
|
||||||
|
|
||||||
|
# Update to latest version
|
||||||
|
git pull
|
||||||
|
docker-compose up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -284,20 +360,16 @@ yarn test
|
|||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
1. **Connection Errors**
|
1. **Node.js Version Error (`positive.toSorted is not a function`)**
|
||||||
|
- This error occurs when using Node.js version lower than 20
|
||||||
|
- Solution: Update to Node.js 20.10.0 or higher using nvm (see Prerequisites section)
|
||||||
|
- Docker users: The container automatically uses the correct Node.js version
|
||||||
|
|
||||||
|
2. **Connection Errors**
|
||||||
- Verify your Home Assistant instance is running
|
- Verify your Home Assistant instance is running
|
||||||
- Check the HASS_HOST is correct and accessible
|
- Check the HASS_HOST is correct and accessible
|
||||||
- Ensure your token has the required permissions
|
- Ensure your token has the required permissions
|
||||||
|
|
||||||
2. **Entity Control Issues**
|
|
||||||
- Verify the entity_id exists in Home Assistant
|
|
||||||
- Check the entity domain matches the command
|
|
||||||
- Ensure parameter values are within valid ranges
|
|
||||||
|
|
||||||
3. **Permission Issues**
|
|
||||||
- Verify your token has write permissions for the entity
|
|
||||||
- Check Home Assistant logs for authorization errors
|
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
### Completed
|
### Completed
|
||||||
|
|||||||
6
src/config/hass.config.ts
Normal file
6
src/config/hass.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const HASS_CONFIG = {
|
||||||
|
BASE_URL: process.env.HASS_HOST || 'http://192.168.178.63:8123',
|
||||||
|
TOKEN: process.env.HASS_TOKEN,
|
||||||
|
SOCKET_URL: process.env.HASS_HOST || 'http://192.168.178.63:8123',
|
||||||
|
SOCKET_TOKEN: process.env.HASS_TOKEN,
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CreateApplication, TServiceParams, StringConfig } from "@digital-alchemy/core";
|
import { CreateApplication, TServiceParams, StringConfig } from "@digital-alchemy/core";
|
||||||
import { LIB_HASS, PICK_ENTITY } from "@digital-alchemy/hass";
|
import { LIB_HASS, PICK_ENTITY } from "@digital-alchemy/hass";
|
||||||
import { DomainSchema } from "../schemas.js";
|
import { DomainSchema } from "../schemas.js";
|
||||||
|
import { HASS_CONFIG } from "../config/hass.config.js";
|
||||||
|
|
||||||
type Environments = "development" | "production" | "test";
|
type Environments = "development" | "production" | "test";
|
||||||
|
|
||||||
@@ -25,19 +26,39 @@ const MY_APP = CreateApplication({
|
|||||||
enum: ["development", "production", "test"],
|
enum: ["development", "production", "test"],
|
||||||
description: "Code runner addon can set with it's own NODE_ENV",
|
description: "Code runner addon can set with it's own NODE_ENV",
|
||||||
} satisfies StringConfig<Environments>,
|
} satisfies StringConfig<Environments>,
|
||||||
HASS_HOST: {
|
|
||||||
type: "string",
|
|
||||||
description: "Home Assistant host URL",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
HASS_TOKEN: {
|
|
||||||
type: "string",
|
|
||||||
description: "Home Assistant long-lived access token",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
services: {},
|
services: {},
|
||||||
libraries: [LIB_HASS],
|
libraries: [
|
||||||
|
{
|
||||||
|
...LIB_HASS,
|
||||||
|
configuration: {
|
||||||
|
BASE_URL: {
|
||||||
|
type: "string",
|
||||||
|
description: "Home Assistant base URL",
|
||||||
|
required: true,
|
||||||
|
default: HASS_CONFIG.BASE_URL
|
||||||
|
},
|
||||||
|
TOKEN: {
|
||||||
|
type: "string",
|
||||||
|
description: "Home Assistant long-lived access token",
|
||||||
|
required: true,
|
||||||
|
default: HASS_CONFIG.TOKEN
|
||||||
|
},
|
||||||
|
SOCKET_URL: {
|
||||||
|
type: "string",
|
||||||
|
description: "Home Assistant WebSocket URL",
|
||||||
|
required: true,
|
||||||
|
default: HASS_CONFIG.SOCKET_URL
|
||||||
|
},
|
||||||
|
SOCKET_TOKEN: {
|
||||||
|
type: "string",
|
||||||
|
description: "Home Assistant WebSocket token",
|
||||||
|
required: true,
|
||||||
|
default: HASS_CONFIG.SOCKET_TOKEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
name: 'hass' as const
|
name: 'hass' as const
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
31
src/index.ts
31
src/index.ts
@@ -3,6 +3,10 @@ import { LiteMCP } from 'litemcp';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { DomainSchema } from './schemas.js';
|
import { DomainSchema } from './schemas.js';
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const HASS_HOST = process.env.HASS_HOST || 'http://192.168.178.63:8123';
|
||||||
|
const HASS_TOKEN = process.env.HASS_TOKEN;
|
||||||
|
|
||||||
interface CommandParams {
|
interface CommandParams {
|
||||||
command: string;
|
command: string;
|
||||||
entity_id: string;
|
entity_id: string;
|
||||||
@@ -41,9 +45,9 @@ async function main() {
|
|||||||
parameters: z.object({}),
|
parameters: z.object({}),
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${process.env.HASS_HOST}/api/states`, {
|
const response = await fetch(`${HASS_HOST}/api/states`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${process.env.HASS_TOKEN}`,
|
Authorization: `Bearer ${HASS_TOKEN}`,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -185,15 +189,26 @@ async function main() {
|
|||||||
|
|
||||||
// Call Home Assistant service
|
// Call Home Assistant service
|
||||||
try {
|
try {
|
||||||
await hass.services[domain][service](serviceData);
|
const response = await fetch(`${HASS_HOST}/api/services/${domain}/${service}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${HASS_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(serviceData),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to execute ${service} for ${params.entity_id}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Successfully executed ${service} for ${params.entity_id}`
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to execute ${service} for ${params.entity_id}: ${error instanceof Error ? error.message : 'Unknown error occurred'}`);
|
throw new Error(`Failed to execute ${service} for ${params.entity_id}: ${error instanceof Error ? error.message : 'Unknown error occurred'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: `Successfully executed ${service} for ${params.entity_id}`
|
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -9,14 +9,16 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"allowJs": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"dist"
|
"dist",
|
||||||
|
"__tests__"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user