Add core API routes, Home Assistant interfaces, and device management tools
- Implemented comprehensive API routes for MCP schema, device execution, and control - Created Home Assistant entity and event interfaces with detailed type definitions - Added tools for listing and controlling Home Assistant devices - Introduced SSE (Server-Sent Events) subscription and management endpoints - Implemented flexible device control with support for multiple domains and parameters
This commit is contained in:
185
src/api/routes.ts
Normal file
185
src/api/routes.ts
Normal file
@@ -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;
|
||||
Reference in New Issue
Block a user