diff --git a/src/api/routes.ts b/src/api/routes.ts
new file mode 100644
index 0000000..412811f
--- /dev/null
+++ b/src/api/routes.ts
@@ -0,0 +1,185 @@
+import { Router } from 'express';
+import { MCP_SCHEMA } from '../mcp/schema.js';
+import { middleware } from '../middleware/index.js';
+import { sseManager } from '../sse/index.js';
+import { v4 as uuidv4 } from 'uuid';
+import { TokenManager } from '../security/index.js';
+import { tools } from '../services/tools.js';
+import { Tool } from '../interfaces/index.js';
+
+const router = Router();
+
+// MCP schema endpoint - no auth required as it's just the schema
+router.get('/mcp', (_req, res) => {
+ res.json(MCP_SCHEMA);
+});
+
+// MCP execute endpoint - requires authentication
+router.post('/mcp/execute', middleware.authenticate, async (req, res) => {
+ try {
+ const { tool: toolName, parameters } = req.body;
+
+ // Find the requested tool
+ const tool = tools.find((t: Tool) => t.name === toolName);
+ if (!tool) {
+ return res.status(404).json({
+ success: false,
+ message: `Tool '${toolName}' not found`
+ });
+ }
+
+ // Execute the tool with the provided parameters
+ const result = await tool.execute(parameters);
+ res.json(result);
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ message: error instanceof Error ? error.message : 'Unknown error occurred'
+ });
+ }
+});
+
+// Health check endpoint
+router.get('/health', (_req, res) => {
+ res.json({
+ status: 'ok',
+ timestamp: new Date().toISOString(),
+ version: '0.1.0'
+ });
+});
+
+// List devices endpoint
+router.get('/list_devices', middleware.authenticate, async (req, res) => {
+ try {
+ const tool = tools.find((t: Tool) => t.name === 'list_devices');
+ if (!tool) {
+ return res.status(404).json({
+ success: false,
+ message: 'Tool not found'
+ });
+ }
+
+ const result = await tool.execute({ token: req.headers.authorization?.replace('Bearer ', '') });
+ res.json(result);
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ message: error instanceof Error ? error.message : 'Unknown error occurred'
+ });
+ }
+});
+
+// Device control endpoint
+router.post('/control', middleware.authenticate, async (req, res) => {
+ try {
+ const tool = tools.find((t: Tool) => t.name === 'control');
+ if (!tool) {
+ return res.status(404).json({
+ success: false,
+ message: 'Tool not found'
+ });
+ }
+
+ const result = await tool.execute({
+ ...req.body,
+ token: req.headers.authorization?.replace('Bearer ', '')
+ });
+ res.json(result);
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ message: error instanceof Error ? error.message : 'Unknown error occurred'
+ });
+ }
+});
+
+// SSE endpoints
+router.get('/subscribe_events', middleware.wsRateLimiter, (req, res) => {
+ try {
+ // Get token from query parameter
+ const token = req.query.token?.toString();
+
+ if (!token || !TokenManager.validateToken(token)) {
+ return res.status(401).json({
+ success: false,
+ message: 'Unauthorized - Invalid token'
+ });
+ }
+
+ // Set SSE headers
+ res.writeHead(200, {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive',
+ 'Access-Control-Allow-Origin': '*'
+ });
+
+ // Send initial connection message
+ res.write(`data: ${JSON.stringify({
+ type: 'connection',
+ status: 'connected',
+ timestamp: new Date().toISOString()
+ })}\n\n`);
+
+ const clientId = uuidv4();
+ const client = {
+ id: clientId,
+ send: (data: string) => {
+ res.write(`data: ${data}\n\n`);
+ }
+ };
+
+ // Add client to SSE manager
+ const sseClient = sseManager.addClient(client, token);
+ if (!sseClient || !sseClient.authenticated) {
+ res.write(`data: ${JSON.stringify({
+ type: 'error',
+ message: sseClient ? 'Authentication failed' : 'Maximum client limit reached',
+ timestamp: new Date().toISOString()
+ })}\n\n`);
+ return res.end();
+ }
+
+ // Subscribe to events if specified
+ const events = req.query.events?.toString().split(',').filter(Boolean);
+ if (events?.length) {
+ events.forEach(event => sseManager.subscribeToEvent(clientId, event));
+ }
+
+ // Subscribe to entity if specified
+ const entityId = req.query.entity_id?.toString();
+ if (entityId) {
+ sseManager.subscribeToEntity(clientId, entityId);
+ }
+
+ // Subscribe to domain if specified
+ const domain = req.query.domain?.toString();
+ if (domain) {
+ sseManager.subscribeToDomain(clientId, domain);
+ }
+
+ // Handle client disconnect
+ req.on('close', () => {
+ sseManager.removeClient(clientId);
+ });
+
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ message: error instanceof Error ? error.message : 'Unknown error occurred'
+ });
+ }
+});
+
+// SSE stats endpoint
+router.get('/get_sse_stats', middleware.authenticate, (_req, res) => {
+ const stats = {
+ clients: sseManager.getClientCount(),
+ events: sseManager.getEventSubscriptions(),
+ entities: sseManager.getEntitySubscriptions(),
+ domains: sseManager.getDomainSubscriptions()
+ };
+ res.json(stats);
+});
+
+export default router;
\ No newline at end of file
diff --git a/src/interfaces/hass.ts b/src/interfaces/hass.ts
new file mode 100644
index 0000000..382ec02
--- /dev/null
+++ b/src/interfaces/hass.ts
@@ -0,0 +1,83 @@
+///
+
+// Home Assistant entity types
+export interface HassEntity {
+ entity_id: string;
+ state: string;
+ attributes: Record;
+ last_changed?: string;
+ last_updated?: string;
+ context?: {
+ id: string;
+ parent_id?: string;
+ user_id?: string;
+ };
+}
+
+export interface HassState {
+ entity_id: string;
+ state: string;
+ attributes: {
+ friendly_name?: string;
+ description?: string;
+ [key: string]: any;
+ };
+}
+
+// Home Assistant instance types
+export interface HassInstance {
+ states: HassStates;
+ services: HassServices;
+ connection: HassConnection;
+ subscribeEvents: (callback: (event: HassEvent) => void, eventType?: string) => Promise;
+ unsubscribeEvents: (subscription: number) => void;
+}
+
+export interface HassStates {
+ get: () => Promise;
+ subscribe: (callback: (states: HassEntity[]) => void) => Promise;
+ unsubscribe: (subscription: number) => void;
+}
+
+export interface HassServices {
+ get: () => Promise>>;
+ call: (domain: string, service: string, serviceData?: Record) => Promise;
+}
+
+export interface HassConnection {
+ socket: WebSocket;
+ subscribeEvents: (callback: (event: HassEvent) => void, eventType?: string) => Promise;
+ unsubscribeEvents: (subscription: number) => void;
+}
+
+export interface HassService {
+ name: string;
+ description: string;
+ target?: {
+ entity?: {
+ domain: string[];
+ };
+ };
+ fields: Record;
+}
+
+export interface HassEvent {
+ event_type: string;
+ data: Record;
+ origin: string;
+ time_fired: string;
+ context: {
+ id: string;
+ parent_id?: string;
+ user_id?: string;
+ };
+}
+
+// Re-export entity types from index.ts
+export { HassEntity, HassState } from './index.js';
\ No newline at end of file
diff --git a/src/services/tools.ts b/src/services/tools.ts
new file mode 100644
index 0000000..953fe93
--- /dev/null
+++ b/src/services/tools.ts
@@ -0,0 +1,101 @@
+import { z } from 'zod';
+import { Tool } from '../interfaces/index.js';
+import { get_hass } from '../hass/index.js';
+import { DomainSchema } from '../schemas.js';
+import { HassEntity, HassState } from '../interfaces/index.js';
+
+// Define tools array
+export const tools: Tool[] = [
+ {
+ name: 'list_devices',
+ description: 'List all devices connected to Home Assistant',
+ parameters: z.object({
+ domain: DomainSchema.optional(),
+ area: z.string().optional(),
+ floor: z.string().optional()
+ }),
+ execute: async (params) => {
+ const hass = await get_hass();
+ const states = await hass.states.get();
+
+ // Filter by domain if specified
+ let filteredStates = states;
+ if (params.domain) {
+ filteredStates = states.filter((state: HassEntity) => state.entity_id.startsWith(`${params.domain}.`));
+ }
+
+ // Filter by area if specified
+ if (params.area) {
+ filteredStates = filteredStates.filter((state: HassEntity) =>
+ state.attributes.area_id === params.area ||
+ state.attributes.area === params.area
+ );
+ }
+
+ // Filter by floor if specified
+ if (params.floor) {
+ filteredStates = filteredStates.filter((state: HassEntity) =>
+ state.attributes.floor === params.floor
+ );
+ }
+
+ return {
+ success: true,
+ devices: filteredStates.map((state: HassEntity) => ({
+ entity_id: state.entity_id,
+ state: state.state,
+ attributes: state.attributes
+ }))
+ };
+ }
+ },
+ {
+ name: 'control',
+ description: 'Control a Home Assistant device',
+ parameters: z.object({
+ command: z.string(),
+ entity_id: z.string(),
+ state: z.string().optional(),
+ brightness: z.number().min(0).max(255).optional(),
+ color_temp: z.number().optional(),
+ rgb_color: z.tuple([z.number(), z.number(), z.number()]).optional(),
+ position: z.number().min(0).max(100).optional(),
+ tilt_position: z.number().min(0).max(100).optional(),
+ temperature: z.number().optional(),
+ target_temp_high: z.number().optional(),
+ target_temp_low: z.number().optional(),
+ hvac_mode: z.string().optional(),
+ fan_mode: z.string().optional(),
+ humidity: z.number().min(0).max(100).optional()
+ }),
+ execute: async (params) => {
+ const hass = await get_hass();
+ const domain = params.entity_id.split('.')[0];
+
+ const serviceData: Record = {
+ entity_id: params.entity_id
+ };
+
+ // Add optional parameters if they exist
+ if (params.state) serviceData.state = params.state;
+ if (params.brightness) serviceData.brightness = params.brightness;
+ if (params.color_temp) serviceData.color_temp = params.color_temp;
+ if (params.rgb_color) serviceData.rgb_color = params.rgb_color;
+ if (params.position) serviceData.position = params.position;
+ if (params.tilt_position) serviceData.tilt_position = params.tilt_position;
+ if (params.temperature) serviceData.temperature = params.temperature;
+ if (params.target_temp_high) serviceData.target_temp_high = params.target_temp_high;
+ if (params.target_temp_low) serviceData.target_temp_low = params.target_temp_low;
+ if (params.hvac_mode) serviceData.hvac_mode = params.hvac_mode;
+ if (params.fan_mode) serviceData.fan_mode = params.fan_mode;
+ if (params.humidity) serviceData.humidity = params.humidity;
+
+ await hass.services.call(domain, params.command, serviceData);
+
+ return {
+ success: true,
+ message: `Command '${params.command}' executed on ${params.entity_id}`
+ };
+ }
+ }
+];
\ No newline at end of file