* docs: restructure MCP section for agent config, update uiServer type in config table
* feat(ui-server): log deprecation warning when HTTP transport is used
* fix(ui-server): harmonize log levels and message patterns with existing transports
* test(ui-server): align MCP tests with style guide
- Use createLoggerMocks() for log assertions instead of trivial checks
- Add server.stop() to factory fallback tests to prevent resource leaks
- Replace fragile globalThis.clearTimeout override with t.mock.method
* docs: restore PDU acronym definition in WebSocket Protocol section
* fix(ui-server): use UI protocol version for MCP server version
* fix(ui-server): derive log file paths from Configuration instead of hardcoding
* perf(ui-server): tail-read log files instead of loading entire file into memory
* refactor(ui-server): convert log resources to tools with tail parameter
Follow MCP best practices: resources are for static snapshots,
tools are for parameterized on-demand data.
- Replace log://combined and log://error resources with
readCombinedLog and readErrorLog tools
- Add tail parameter (default 200, max 5000) following the
filesystem server head/tail pattern
- Add readOnlyHint annotation per MCP tool spec
- Return totalLines metadata in response for context
- Keep station and template resources (small, static data)
* feat(ui-server): expose OCPP JSON schemas as MCP resource templates
Add schema://ocpp/{version} listing and schema://ocpp/{version}/{command}
resource templates that serve the existing JSON schema files from
src/assets/json-schemas/ocpp/ on demand.
LLM agents can discover available OCPP commands per version and read
the full JSON schema for any command without bloating tools/list.
Schemas are read from disk on demand — no memory duplication with
the AJV validators already loaded by OCPP services.
- Add ocppSchemaMapping linking ProcedureName to JSON Schema files
- 16 OCPP tools now have ocpp16Payload/ocpp20Payload fields with
version affinity in descriptions
- Intercept tools/list to inject full JSON Schemas from disk
into inputSchema.properties (no Zod duplication, DRY)
- Add payload flattening: extract versioned payload, spread flat
- Add mutual exclusivity: reject if both payloads provided
- Add pre-flight version check: clear error on version mismatch
- Runtime guard: graceful fallback if SDK internal API changes
* refactor(ui-server): use OCPPVersion enum instead of string literals
* refactor(ui-server): improve MCP code elegance and project rule compliance
- Replace as-unknown-as cast with Reflect.get() for SDK handler access
- Replace generic Error with BaseError in stop/readRequestBody
- Extract createToolErrorResponse/createToolResponse static helpers (DRY)
- Extract closeTransportSafely for duplicate transport cleanup
- Extract registerLogReadTool factory for duplicate log tools (DRY)
- Remove unnecessary String() conversions in resource handlers
* fix(ui-server): fix body size double-counting and schema resource path
- readRequestBody: pass chunk.length (not accumulated total) to
createBodySizeLimiter — matches UIHttpServer pattern
- getSchemaBaseDir: use correct relative path instead of broken
resolve().includes('assets') condition (always true)
* refactor(ui-server): consolidate version fallback guards and clarify README version docs
* fix(ui-server): harden MCP schema resources against path traversal and fix transport leak
- Add path traversal guard in schema resource handlers to validate resolved
paths stay within the OCPP schema base directory
- Close MCP transport on 405 Method Not Allowed responses to prevent leak
- Fix log file path to use direct path when rotation is disabled
- Fix misleading Heartbeat tool description that referenced unsupported
ocpp payload fields
* fix(ui-server): address remaining review findings and lint warnings
- Reorder stop() to delete-before-reject preventing re-entrant issues
- Use ephemeral port in integration test instead of fixed port 18999
- Document synchronous authenticate() assumption and SDK pattern deviation
- Add 'modelcontextprotocol' to cspell wordlist
- Convert JSDoc to inline comment to fix jsdoc lint warnings
- Use exact pathname match (url.pathname !== '/mcp') instead of
startsWith to prevent unintended paths from reaching the MCP handler
- Pin @modelcontextprotocol/sdk to ~1.27.1 (patch-only) since the
OCPP schema injection relies on SDK internals pinned to 1.27.x
- Add startup log confirming successful OCPP JSON schema injection
* test(ui-server): add comprehensive UIMCPServer unit tests
Cover private method logic missing from initial test suite:
- invokeProcedure: service null, dual payload, version mismatch,
direct response, service error, 30s timeout (mock timers)
- checkVersionCompatibility: OCPP 1.6/2.0/2.0.1 matching, cross-version
errors, hashIds filtering, all-stations fallback
- readRequestBody: valid JSON, payload too large, invalid JSON, stream error
- loadOcppSchemas: disk loading, cache entry validation
Refactor for elegance and guide compliance:
- Centralize Reflect.get wrappers in TestableUIMCPServer class
- Extract assertToolError() helper to eliminate repeated triplet
- Move createMockChargingStationDataWithVersion() to UIServerTestUtils
- Replace magic number with DEFAULT_MAX_PAYLOAD_SIZE import
* fix(ui-server): per-request McpServer factory, async iterator body reader, transport leak
- Create a new McpServer per HTTP request via createMcpServer() factory
to fix concurrency bug: McpServer.connect() overwrites a single
internal _transport field, causing cross-talk under concurrent requests.
OCPP schema cache and UI service are pre-loaded at startup; tool
registration is ~12µs per request (negligible).
- Replace event-listener readRequestBody with for-await-of async
iterator pattern. Single settlement guaranteed by language semantics,
eliminating the double-reject bug on payload-too-large.
- Close transport on mcpServer.connect() failure to prevent resource leak.
- Clean up both transport and McpServer on response close via unified
cleanup callback.
* fix: align StatusNotification field name with OCPP 2.0.1 schema, fix asset paths
- Use 'connectorStatus' (OCPP 2.0.1 schema field name) instead of
'status' (non-standard) in OCPP20RequestService.buildRequestPayload
and both call sites in OCPP20IncomingRequestService
- Fix esbuild-relative asset paths for OCPP JSON schemas and MCP
resource handlers — import.meta.url resolves to dist/start.js,
not the original source tree depth
* refactor(ocpp): align payload builders to object-param API, fix StatusNotification field names
- Refactor buildStatusNotificationRequest from positional params to
object-based StatusNotificationRequest input. Builder accepts both
'status' (internal/OCPP 1.6) and 'connectorStatus' (OCPP 2.0.1)
with consistent fallback order (connectorStatus ?? status).
- Refactor buildTransactionEvent from 3 overloads to single
object-based OCPP20TransactionEventRequest input, removing
positional param complexity and non-null assertions.
- Refactor sendAndSetConnectorStatus from positional params to
object-based StatusNotificationRequest input. Callers pass
version-correct field names: 'status' in OCPP 1.6 code,
'connectorStatus' in OCPP 2.0 code, either in common code.
- Extract getSchemaBaseDir() as protected method on UIMCPServer
for test overridability, replacing fragile symlink hack.
- Migrate all call sites (28 sendAndSetConnectorStatus, 60+
buildTransactionEvent) and their corresponding tests.
* build: externalize @modelcontextprotocol/sdk and zod from esbuild bundle
Add @modelcontextprotocol/* and zod to esbuild external list for
consistency with all other production dependencies (ajv, ws, winston,
etc.) which are already externalized. Reduces bundle size and avoids
bundling the SDK's transitive dependency tree.
- Add localeCompare to schema file sort in MCPResourceHandlers to
ensure locale-aware alphabetical ordering (SonarCloud bug)
- Extract sendErrorResponse helper from handleMcpRequest to reduce
cognitive complexity from 16 to below threshold (SonarCloud smell)
* chore: update .serena/project.yml with new default config fields
* refactor(ocpp): buildMeterValue resolves connectorId/evseId from transactionId
- Refactor buildMeterValue signature from (station, connectorId,
transactionId, interval) to (station, transactionId, interval).
connectorId and evseId are resolved internally from the unique
transactionId via getConnectorIdByTransactionId/getEvseIdByTransactionId.
- Refactor getSampledValueTemplate to accept optional evseId. When
provided, EVSE-level MeterValues templates take priority; falls back
to merging connector-level templates from all EVSE connectors.
- Add MeterValues field to EvseStatus and EvseTemplate types to support
EVSE-level meter value template definitions.
- Refactor handleMeterValues in BroadcastChannel from if/else to
switch/case on OCPP version with explicit default error.
- Simplify OCPP20IncomingRequestService fallback MeterValues (no
transaction = static empty meter value, skip buildMeterValue).
- Add buildMeterValue unit tests covering OCPP 1.6/2.0 resolution,
unknown transactionId throw, EVSE-level template priority, and
connector template merge fallback.
- Add TEST_TRANSACTION_ID_STRING constant for OCPP 2.0 tests, fix
BroadcastChannel test to use string transactionId for OCPP 2.0.1.
- Fix commandResponseToResponseStatus for Authorize: use switch/case
on OCPP version. OCPP 1.6 checks idTagInfo.status, OCPP 2.0 checks
idTokenInfo.status. Previously only checked idTagInfo (1.6 field),
causing MCP to report 'failure' for successful OCPP 2.0 authorizations.
- Make AuthorizeResponse and AuthorizeRequest cross-version union types
(OCPP16 | OCPP20) consistent with AuthorizationStatus pattern.
- Make isIdTagRemoteAuthorized version-aware: sends idTag for OCPP 1.6,
idToken for OCPP 2.0, and checks the version-correct response field.
- Use version-specific casts (OCPP16AuthorizeResponse,
OCPP20AuthorizeResponse) in version-switched code paths instead of
agnostic union type.
* refactor(ocpp): harmonize version switch/case in auth functions
- Refactor isIdTagAuthorizedUnified and isIdTagAuthorized from if/else
on OCPP version to switch/case with explicit VERSION_16, VERSION_20/201
cases and separate default fallback.
- Remove unnecessary optional chaining on stationInfo inside
version-switched cases where stationInfo is guaranteed non-null.
- Separate VERSION_16 from default in all auth switch/case blocks
for spec-compliant version handling.
* fix(ocpp): buildMeterValue from transactionId, log date locale fix, MeterValues EVSE templates
- Refactor buildMeterValue to resolve connectorId/evseId from
transactionId internally. Returns empty MeterValue when transactionId
is undefined (MCP pass-through with provided meterValue).
- Fix log file date resolution: use local date instead of UTC to match
Winston DailyRotateFile naming convention.
- Add optional date parameter to readCombinedLog/readErrorLog MCP tools
for accessing rotated log files by date (YYYY-MM-DD).
- Add MeterValues field to EvseStatus/EvseTemplate types for EVSE-level
meter value template definitions. getSampledValueTemplate checks
EVSE-level templates first, falls back to merging connector templates.
- Refactor handleMeterValues to switch/case on OCPP version with
explicit default error.
- Make AuthorizeRequest/AuthorizeResponse cross-version union types.
Fix commandResponseToResponseStatus and isIdTagRemoteAuthorized with
version-aware switch/case for OCPP 1.6 (idTagInfo) vs 2.0 (idTokenInfo).
- Add integration tests for readCombinedLog tool (default date, explicit
date, missing file error). Add buildMeterValue unit tests.