Managing one smart lock from a phone is easy. Managing access across multiple rental properties is not. I built eufy-lock-codes to make that work reviewable and recoverable instead of relying on a long sequence of manual changes in a mobile app.
It is a local MCP server for managing Eufy smart-lock access codes across rental properties. Code changes are planned first, write operations require an explicit second step, stored plans are redacted, and successful writes are recorded in a private local escrow when Eufy does not return plaintext.
The system uses the unofficial eufy-security-client package. Eufy does not provide a stable public smart-lock API, so this project keeps the Eufy integration behind a backend adapter and treats live verification as a maintainer gate.
- Discovers Eufy smart locks and reports capability flags.
- Lists lock-code users and passcode metadata for one lock, one property, or all configured properties.
- Creates dry-run plans for creating, updating, deleting, and rotating codes.
- Executes exactly one unexpired confirmation token.
- Atomically claims confirmation tokens so one pending plan cannot be executed twice.
- Waits for Eufy user-event acknowledgments, then verifies final user-list state.
- Stores locally created or updated plaintext passcodes in ignored private escrow.
- Writes redacted audit logs and live-test backups under ignored local state.
It never performs lock or unlock commands.
- Write operations require a plan first, then
execute_plan. - Plans expire and cannot be reused after execution.
- Pending plan files contain masked operations. Plaintext needed for execution is stored separately under ignored local state, deleted when a plan is claimed, and cleaned during expiry maintenance.
- Public tool responses and audit logs mask passcodes.
- Ambiguous usernames, missing mappings, unsupported locks, and failed list calls are hard stops.
- Rotation creates or updates the replacement before deleting an old user when the username changes.
- If a later operation fails after new users were created, the executor attempts to delete those newly created users to avoid leaving extra active access.
- Live verification scripts require
--yes-live-writeorEUFY_CONFIRM_LIVE_WRITE=1.
mcp/server.mjsexposes MCP tools over stdio.src/tools.mjsimplements planning, target resolution, safety checks, and execution.src/backend/eufy-adapter.mjsisolates the unofficial Eufy client and waits for user-event acknowledgments.src/plan-store.mjspersists redacted plans, short-lived pending secrets, expiry cleanup, and redacted audit records underdata/.src/escrow.mjsstores plaintext for locally created or updated codes under ignored local state.src/recovery-cache.mjscan merge previously recovered local inventory into masked list responses when private recovery files exist.
discover_locks: list Eufy smart locks and capability flags.health_check: verify credentials, Eufy connectivity, config, and mapped lock availability.list_lock_codes: list users and passcode metadata without returning full plaintext passcodes.plan_create_code: create a dry-run add-user/code plan.plan_update_code: create a dry-run passcode or schedule update plan.plan_delete_code: create a dry-run exact-username delete plan.plan_rotate_codes: create a dry-run tenant or maintenance rotation plan.execute_plan: execute one unexpired confirmation token.
Requirements:
- Node.js 24 or newer
- A Eufy account with supported smart locks
Install dependencies:
npm ciCreate local configuration:
cp .env.example .env
cp config/properties.example.yaml config/properties.local.yamlFill .env with:
eufy_email=your-account@example.com
eufy_pass=your-password
EUFY_COUNTRY=US
EUFY_LANGUAGE=enFill config/properties.local.yaml with your real property aliases and lock serials. Local configs are ignored by git.
Run the MCP server:
node mcp/server.mjsRun the no-credentials demo:
npm run demoRun local checks:
npm run checkRun test coverage:
npm run coverageRun a read-only Eufy smoke check with real local credentials:
npm run smokeRun live CRUD verification against one configured test lock:
EUFY_LIVE_TEST_PROPERTY=sample-property \
EUFY_LIVE_TEST_LOCK_ALIAS=front \
npm run test:live -- --yes-live-writeThe live test creates and removes temporary users, creates and removes one scheduled expiring code, writes before/after backups under data/backups/, and verifies the test users are gone. It does not lock or unlock the door.
- Run
health_check. - Run
list_lock_codesfor the target property or lock. - Run a
plan_*tool. - Review the dry-run operations and confirmation token.
- Run
execute_planonly for the intended token. - Re-run
list_lock_codesto verify final state.
- Eufy smart-lock APIs are unofficial and can drift.
- Existing plaintext PINs are not always available from Eufy cloud responses.
- Passcode value verification is limited by what Eufy returns; writes are verified through acknowledgements, final user-list state, and local escrow.
- Offline or low-battery locks may not answer live P2P/read operations.
- Production use should keep local backups and use a designated live verification lock after backend changes.
See docs/threat-model.md and docs/verification.md for the safety assumptions and maintainer verification gate.
AGPL-3.0-only.