Skip to main content
Glama

Plugwise MCP Server

by Tommertom
refactoring-visual-guide.md17.7 kB
# Code Refactoring Visual Guide ## Current Architecture vs. Proposed Architecture ### BEFORE: Current Structure ``` ┌─────────────────────────────────────────────────────────────┐ │ PlugwiseClient │ │ (550 lines - MONOLITHIC) │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ HTTP Communication │ │ │ │ - request(), parseXml() │ │ │ │ - Basic Auth, timeout handling │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Gateway Detection │ │ │ │ - connect(), detectGatewayType() │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Device Parsing │ │ │ │ - parseAppliance(), parseLocation() │ │ │ │ - parseMeasurements(), parseActuators() │ │ │ │ (200+ lines of nested logic) │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Control Operations │ │ │ │ - setTemperature(), setPreset() │ │ │ │ - setSwitch(), setGatewayMode() │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ Issues: ❌ 550 lines in one file ❌ Multiple responsibilities ❌ Hard to test individual components ❌ Complex nested logic ``` --- ### AFTER: Proposed Modular Structure ``` ┌────────────────────────────────────────────────────────────────┐ │ PlugwiseClient │ │ (80 lines - ORCHESTRATOR) │ │ │ │ constructor(config) │ │ async connect() → delegates to GatewayParser │ │ async getDevices() → delegates to Parsers │ │ async setTemperature() → delegates to TempController │ │ async setSwitch() → delegates to SwitchController │ └────────────────────────────────────────────────────────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ HTTP │ │ Parsers │ │ Controllers │ │ Client │ │ │ │ │ └──────────┘ └──────────┘ └──────────────┘ ``` #### HTTP Layer (60 lines) ``` HttpClient ├── request(endpoint, method, data) ├── buildAuthHeader(username, password) └── handleResponse(response) ``` #### Parsing Layer (270 lines split into 4 modules) ``` Parsers/ ├── GatewayParser (70 lines) │ ├── parseGatewayInfo(xml) │ ├── detectGatewayType(gateway) │ └── extractGatewayIds(data) │ ├── ApplianceParser (80 lines) │ ├── parseAppliance(appliance) │ └── extractDeviceInfo(appliance) │ ├── MeasurementParser (60 lines) │ ├── parseMeasurements(source, entity) │ ├── processLogs(logs, suffix) │ └── extractMeasurement(log) │ └── ActuatorParser (70 lines) ├── parseActuators(source, entity) ├── parseRelays(funcs, entity) ├── parseThermostats(funcs, entity) └── parseTemperatureOffsets(funcs, entity) ``` #### Control Layer (120 lines split into 3 modules) ``` Controllers/ ├── TemperatureController (50 lines) │ ├── setTemperature(locationId, params) │ ├── setPreset(locationId, preset) │ └── setTemperatureOffset(deviceId, offset) │ ├── SwitchController (40 lines) │ ├── setSwitch(applianceId, state) │ └── setSwitchLock(applianceId, locked) │ └── GatewayController (30 lines) ├── setGatewayMode(mode) ├── setDHWMode(mode) └── rebootGateway() ``` --- ## Service Layer Simplification ### BEFORE: Duplicated File I/O ``` HubDiscoveryService DeviceStorageService Server.ts │ │ │ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ ensureHubsDir│ │ensureDevsDir │ │getHubsSync() │ │ saveHubToFile│ │saveDevices() │ │getDevsSync() │ │ loadHubFiles │ │loadDevices() │ │ formatHubs() │ │ │ │ │ │ formatDevs() │ └──────────────┘ └──────────────┘ └──────────────┘ ❌ Same logic repeated 3 times ❌ ~150 lines of duplication ``` ### AFTER: Unified Storage Service ``` All Services │ ▼ ┌────────────────────┐ │ JsonStorageService │ (Generic, reusable) │ <T> │ ├────────────────────┤ │ save(file, data) │ │ load(file) │ │ loadAll() │ │ exists(file) │ │ delete(file) │ └────────────────────┘ │ ┌────────┴────────┐ │ │ ▼ ▼ hubStorage deviceStorage (HubData) (DeviceData) ✅ Single implementation ✅ Type-safe ✅ Testable ✅ ~50 lines total ``` --- ## Tool Handler Simplification ### BEFORE: Repeated Boilerplate (15+ tools) ```javascript // temperature.tool.ts (6 tools × 30 lines each) registry.registerTool('set_temperature', {...}, async (args) => { try { const client = connectionService.ensureConnected(); // ← Repeated const data = await client.setTemperature(args); const output = { success: true, data }; // ← Repeated return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], // ← Repeated structuredContent: output }; } catch (error) { // ← Repeated const output = { success: false, error: (error as Error).message }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output }; } }); // switch.tool.ts - SAME CODE // device.tool.ts - SAME CODE // gateway.tool.ts - SAME CODE // ... 12 more times ❌ ~200 lines of duplicated error handling ``` ### AFTER: Helper Functions ```javascript // tool-helpers.ts (30 lines) export async function withConnection<T>( connectionService: ConnectionService, operation: (client: PlugwiseClient) => Promise<T> ): Promise<ToolResponse> { try { const client = connectionService.ensureConnected(); const data = await operation(client); return successResponse(data); } catch (error) { return errorResponse(error); } } // Usage in ALL tools (3 lines each!) registry.registerTool('set_temperature', {...}, async (args) => { return withConnection(connectionService, (client) => client.setTemperature(args) ); }); registry.registerTool('control_switch', {...}, async (args) => { return withConnection(connectionService, (client) => client.setSwitch(args.appliance_id, args.state) ); }); ✅ Each tool now 3 lines instead of 30 ✅ Consistent error handling ✅ Easy to add features (logging, metrics, etc.) ``` --- ## XML Parsing Simplification ### BEFORE: Repetitive Array/Measurement Handling ```javascript // Repeated ~10 times throughout parsers const logs = Array.isArray(source.logs.point_log) ? source.logs.point_log : [source.logs.point_log]; // Repeated ~6 times let measurementValue; if (log.period && log.period.measurement) { const meas = log.period.measurement; measurementValue = typeof meas === 'object' && meas._ !== undefined ? meas._ : meas; } else if (log.measurement) { const meas = log.measurement; measurementValue = typeof meas === 'object' && meas._ !== undefined ? meas._ : meas; } const value = parseFloat(measurementValue); if (isNaN(value)) continue; ❌ ~80 lines of repeated logic ``` ### AFTER: Helper Functions ```javascript // xml-helpers.ts (20 lines) export function ensureArray<T>(value: T | T[] | undefined): T[] { if (!value) return []; return Array.isArray(value) ? value : [value]; } export function extractMeasurement(log: any): number | undefined { const source = log.period?.measurement ?? log.measurement; if (!source) return undefined; const value = typeof source === 'object' && source._ !== undefined ? source._ : source; const num = parseFloat(value); return isNaN(num) ? undefined : num; } // Usage (clean and readable!) const logs = ensureArray(source.logs.point_log); for (const log of logs) { const value = extractMeasurement(log); if (value !== undefined) { entity.sensors[log.type] = value; } } ✅ Reusable helpers ✅ Much cleaner ✅ Easy to test ``` --- ## File Structure Comparison ### BEFORE ``` src/ ├── client/ │ └── plugwise-client.ts ← 550 lines! 🔴 ├── services/ │ ├── connection.service.ts ← 65 lines (simple wrapper) │ ├── device-storage.service.ts ← 120 lines (file I/O) │ └── hub-discovery.service.ts ← 250 lines (complex logic) ├── mcp/ │ ├── server.ts ← 293 lines (sync + async duplication) │ ├── tool-registry.ts ← 85 lines │ └── tools/ │ ├── temperature.tool.ts ← 319 lines (6 tools with boilerplate) │ ├── switch.tool.ts ← 150 lines │ ├── device.tool.ts ← 80 lines │ ├── gateway.tool.ts ← 200 lines │ └── ... (5 more tool files) Total: ~3,500 lines Duplication: ~28% Largest file: 550 lines ``` ### AFTER ``` src/ ├── client/ │ ├── plugwise-client.ts ← 80 lines (orchestrator) │ ├── http-client.ts ← 60 lines │ ├── xml-helpers.ts ← 40 lines │ ├── parsers/ │ │ ├── gateway-parser.ts ← 70 lines │ │ ├── appliance-parser.ts ← 80 lines │ │ ├── measurement-parser.ts ← 60 lines │ │ └── actuator-parser.ts ← 70 lines │ └── controllers/ │ ├── temperature-controller.ts ← 50 lines │ ├── switch-controller.ts ← 40 lines │ └── gateway-controller.ts ← 30 lines ├── services/ │ ├── connection.service.ts ← 65 lines (unchanged) │ └── storage.service.ts ← 50 lines (unified!) ├── mcp/ │ ├── server.ts ← 150 lines (simplified!) │ ├── tool-registry.ts ← 85 lines (unchanged) │ ├── tool-helpers.ts ← 30 lines (NEW - reduces duplication) │ └── tools/ │ ├── temperature.tool.ts ← 80 lines (60% reduction!) │ ├── switch.tool.ts ← 50 lines │ ├── device.tool.ts ← 40 lines │ ├── gateway.tool.ts ← 70 lines │ └── ... (5 more, all smaller) Total: ~2,400 lines (31% reduction) Duplication: ~6% Largest file: 150 lines ``` --- ## Testing Strategy ### BEFORE ``` scripts/ ├── test-mcp-connection.ts ← Manual script ├── test-device-discovery.ts ← Manual script └── test-agent-mcp.ts ← Manual script ❌ No automated tests ❌ Requires real hardware ❌ No CI/CD ``` ### AFTER ``` tests/ ├── unit/ │ ├── client/ │ │ ├── http-client.test.ts │ │ ├── parsers/ │ │ │ ├── gateway-parser.test.ts │ │ │ ├── measurement-parser.test.ts │ │ │ └── ... │ │ └── controllers/ │ │ └── temperature-controller.test.ts │ ├── services/ │ │ ├── storage.service.test.ts │ │ └── connection.service.test.ts │ └── mcp/ │ ├── tool-helpers.test.ts │ └── tools/ │ └── temperature.tool.test.ts ├── integration/ │ ├── full-workflow.test.ts │ └── multi-hub.test.ts └── mocks/ ├── mock-plugwise-hub.ts └── mock-xml-responses.ts ✅ Unit tests for all modules ✅ Integration tests ✅ Mock hub for testing ✅ CI/CD ready ✅ 60-80% coverage ``` --- ## Impact Summary | Category | Before | After | Improvement | |----------|--------|-------|-------------| | **Lines of Code** | 3,500 | 2,400 | -31% | | **Largest File** | 550 lines | 150 lines | -73% | | **Code Duplication** | 28% | 6% | -79% | | **Files** | 32 | 45 | +40% (smaller, focused) | | **Avg File Size** | 110 lines | 55 lines | -50% | | **Cyclomatic Complexity** | High | Low | -60% | | **Test Coverage** | 0% | 70% | +70% | | **Maintainability Index** | 65/100 | 85/100 | +31% | --- ## Migration Path ``` Phase 1: Foundation Phase 2: Client Phase 3: Services (2 weeks) (2 weeks) (2 weeks) ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Add Zod │ │ Split client │ │ Unify storage│ │ Add helpers │ → │ Extract │ → │ Refactor │ │ Setup tests │ │ parsers │ │ discovery │ └──────────────┘ └──────────────┘ └──────────────┘ ↓ Phase 4: Server (2 weeks) ┌──────────────┐ │ Remove sync │ │ Lazy loading │ │ Performance │ └──────────────┘ ``` --- **See Also:** - `code-simplification-analysis.md` - Detailed analysis - `simplification-recommendations-summary.md` - Quick reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Tommertom/plugwise-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server