kwikset-mcp
This server lets you manage Kwikset Halo-family smart locks through MCP: discover locks, check their status, lock/unlock doors, and debug API responses.
List all locks on the account with status, battery percentage, and home; get device IDs for other tools
Get detailed status, battery, model, and serial for a single lock
Lock a specific door
Unlock a specific door, but only when explicitly confirmed by the user (
confirm=true); otherwise it's a no-opDump raw home/device JSON from Kwikset's API for diagnosing missing/null fields
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@kwikset-mcpis my front door locked?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
kwikset-mcp
An MCP server that lets Claude check and control Kwikset Halo-family smart locks (Halo, Halo Keypad, Halo Touch, Halo Select / Select Plus).
This is a Node.js rewrite of an earlier Python version, switched to
avoid Python-specific setup friction on Windows (missing interpreter,
virtualenv activation, PATH aliasing). Node has no exact equivalent of
aiokwikset (the Python library the first version depended on), so this
version talks to Kwikset's cloud API directly: AWS Cognito for login, plus
a small REST API for homes/devices/lock/unlock.
Where the API details came from: Kwikset publishes no official API.
The Cognito pool/client IDs, API host, and REST paths this server uses
were extracted from the published, Apache-2.0-licensed source of
homebridge-kwikset-halo
(a community Homebridge plugin for these same locks), which in turn credits
aiokwikset for first documenting them. See the comments in src/const.js
and src/cognito.js for specifics. This is not officially supported by
Kwikset and could break if they change something server-side.
Testing status: every piece of logic in this server — the Cognito
login flow (including the two-step phone-verification challenge), token
refresh and persistence, the REST field-mapping, the unlock_door
confirmation guard, and every MCP tool end-to-end — was exercised against
hand-written mocks standing in for AWS Cognito, Kwikset's REST API, and the
MCP SDK, and all of it passes. What could not be tested here is calling
the real Kwikset service, since that needs an actual account and a
physical lock. Treat your first list_locks call as the real smoke test —
if it fails outright (not just one field showing null), the pool/host
constants may be stale; if a tool returns nulls, run debug_raw_devices
to see the real field names and compare against src/kwikset-client.js.
If a tool call hangs and the MCP host eventually reports a generic
"timed out after 60s," that's the host's own outer timeout, not this
server's — every network call here (Cognito refresh, every REST request)
now has its own shorter internal timeout (15-20s) that fails with a
specific, readable error instead. If you see the generic 60s timeout
anyway, something below that layer is stuck (e.g. no network route to
AWS/Kwikset from this machine) — check that node auth-setup.js can
reach the internet at all.
Why login is a separate step
Your Kwikset password should never pass through an LLM conversation. So
authentication happens once, in a plain terminal, via auth-setup.js,
which saves only the resulting session tokens locally (never the
password). The MCP server reads and silently refreshes those tokens on
every connection — it never asks Claude, or you via Claude, for your
password.
Related MCP server: Lutron Caseta MCP Server
Setup
Install Node.js (18+) if you don't have it: nodejs.org — get the LTS installer. Verify with:
node --versionInstall dependencies, from inside this folder:
npm installLog in once. Credentials can be given three ways, checked in this order:
# 1. CLI flags (any OS/shell) node auth-setup.js --email you@example.com --password "hunter2"# 2. Environment variables (preferred over the flag above - a # command-line password is visible to other processes/users on the # machine and lands in shell history) # macOS / Linux KWIKSET_EMAIL=you@example.com KWIKSET_PASSWORD='hunter2' node auth-setup.js# Windows PowerShell $env:KWIKSET_EMAIL = "you@example.com" $env:KWIKSET_PASSWORD = "hunter2" node auth-setup.js# 3. Interactive prompt (any OS/shell; default if nothing else is given) node auth-setup.jsIf your account requires phone verification you'll be prompted for the code Kwikset texts you (or pass one up front with
--mfa-code/$KWIKSET_MFA_CODE). This writes~/.kwikset-mcp/tokens.json(owner-read/write only) and never touches your password again after this step.Point Claude at the server.
For Claude Code, from this project directory:
# macOS / Linux claude mcp add kwikset -- node "$(pwd)/src/server.js"# Windows PowerShell claude mcp add kwikset -- node "$PWD\src\server.js"For Claude Desktop, add this to your
claude_desktop_config.json(Settings → Developer → Edit Config), substituting the absolute path to this folder'ssrc/server.js:{ "mcpServers": { "kwikset": { "command": "node", "args": ["/absolute/path/to/kwikset-mcp/src/server.js"] } } }On Windows, use double backslashes in the JSON path, e.g.
"C:\\Users\\you\\kwikset-mcp\\src\\server.js".Then restart the Claude client so it picks up the new server.
Try it. Ask Claude something like "list my Kwikset locks" or "is the front door locked?" — it should call
list_locks/get_lock_status. Unlocking requires an explicit ask, sinceunlock_doorrefuses to act unless called withconfirm=true, which Claude will only do when you've clearly asked it to unlock that door.
Tools exposed
Tool | Description |
| All locks on the account, with status, battery %, home |
| Status/battery/model/serial for one lock |
| Lock a door |
| Unlock a door ( |
| Diagnostic: raw, unprocessed home/device JSON from Kwikset's API |
| Codes this server has created — see below |
| Add a code — see below |
| Remove a code — see below |
| Diagnostic: raw CRC/checksum manifest from Kwikset's API |
Access-code tools: real endpoints, with known v1 limitations
Unlike the guessed usercodes endpoint this project started with (which
returned a 403 against a real account), add_access_code/
remove_access_code/list_access_codes now talk to Kwikset's real
access-code endpoints — POST/PATCH/DELETE prod_v1/devices/{id}/accesscode
— using the real wire format, both confirmed by decompiling the actual
Kwikset Android app (com.kwikset.blewifi) with jadx. The request body is
{"message": "<Base64>"}, where the Base64 decodes to a small TLV8
(Type-Length-Value) binary record — not JSON — packing the slot index, an
enabled flag, and the code's digits as packed BCD. No encryption or device
pairing is involved anywhere in this flow. See src/tlv8.js and
src/access-code-codec.js for the byte-level implementation, and the
"DoorLock" project notes for the full reverse-engineering trace.
That said, this is a v1 with real, documented gaps versus the official app — not guesses, but genuine capability limits of this implementation:
No live "list codes" read. Kwikset's API has no endpoint to read codes back off a lock — only CRC/checksum manifests meant for the lock itself to verify sync, not for an app to read code contents. The real Kwikset app instead relies on a local on-device cache built from background Bluetooth/cloud sync, which this server doesn't have. So
list_access_codesonly shows codes this server has created — it won't reflect codes added via the Kwikset app or the keypad, and if you add/remove a code some other way, this server's record goes stale.Schedules supported.
add_access_codeaccepts an optionalschedule(permanent,date_range, orweekly) — the real Schedule TLV8 byte format, reverse-engineered from the decompiled app. All date/time fields are local wall-clock time, not UTC/epoch.No edit. The real edit/modify request was never reverse-engineered. Remove the old code and add a new one instead.
Slot collisions are possible. Because there's no live read, slot numbers are tracked locally, with no visibility into slots already used by codes set outside this server. Confirmed on real hardware: a manufacturer/factory-default code occupies one of the low slot numbers
colliding with it silently fails (or is rejected by the Kwikset app) even though the create request itself returns success. Automatic allocation now starts at slot 10 to avoid this; check the Kwikset app for existing codes before adding one here, or pass an explicit
slot.
Concretely: treat your first add_access_code call as the real test —
verify the result in the Kwikset app or at the keypad afterward. If
it fails outright, debug_raw_access_codes shows the raw manifest
response, and the tool's own raw_response/sync_status fields show
exactly what Kwikset's API returned for the create/delete call itself.
Re-authenticating
If tokens expire and can't silently refresh (e.g. the Kwikset password was
changed, or 2FA was reset), tool calls will return an auth_required
error with instructions — just re-run node auth-setup.js.
Files
kwikset-mcp/
├── auth-setup.js # run once, by hand, to log in
├── src/
│ ├── const.js # Cognito pool/client IDs, API host (see caveats above)
│ ├── auth.js # local token file read/write
│ ├── cognito.js # AWS Cognito login/refresh (amazon-cognito-identity-js)
│ ├── util.js # shared request-timeout helper
│ ├── tlv8.js # TLV8 record encoding + type-byte enums (reverse-engineered)
│ ├── access-code-codec.js # builds the real create/delete access-code byte payloads
│ ├── access-code-store.js # local record of codes this server has set (~/.kwikset-mcp/access-codes.json)
│ ├── kwikset-client.js # REST calls: homes, devices, lock/unlock, access codes
│ └── server.js # MCP server + tool definitions
├── package.json
└── .gitignoreAvailable Tools
5 toolsdebug_raw_devicesA
Diagnostic tool: dumps the raw, unprocessed home/device JSON straight from Kwikset's API. Use this only when another tool returns null fields (name/status/battery, etc) - that means Kwikset renamed a field this server looks for, and this shows the real field names.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full disclosure burden. It clearly states this is a diagnostic read-only dump and explains the failure mode it addresses. It doesn't explicitly state side effects or caveats, but 'dumps' combined with 'Diagnostic tool' implies no mutation, and the scope is well defined.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is exactly two sentences, with the most essential fact ('Diagnostic tool') front-loaded. Every clause earns its place: the source of data, the triggering condition, and the reason for the null-field symptom. No filler or repetition exists.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description fully covers what an agent needs: when to invoke this tool, what it returns, and why the output matters. No parameters or output schema are required, and the diagnostic purpose is unlikely to require further detail about return values or error handling for correct selection and invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters and a 100% schema description coverage (vacuously, since the schema is empty). The description explains the nature of the output ('raw, unprocessed... JSON') and the diagnostic trigger, which is sufficient because no parameter guidance is needed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action ('dumps the raw, unprocessed home/device JSON') and a clear resource ('straight from Kwikset's API'). It identifies itself as a 'Diagnostic tool,' which immediately distinguishes it from the sibling commands that perform listing, status, and locking actions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives an explicit condition for when to use it: 'Use this only when another tool returns null fields.' It also explains the reasoning (Kwikset renamed a field) and the expected value (shows real field names), leaving no ambiguity about when to choose this tool over siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_lock_statusA
Get the current status (locked/unlocked/jammed), battery level, and model/serial for one lock. Get device_id from list_locks first.
| Name | Required | Description | Default |
|---|---|---|---|
| device_id | Yes | A lock's device_id, from list_locks |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the disclosure burden. It indicates a read-only retrieve operation by the word 'Get' and lists the data returned, but it does not explicitly state side-effect-free behavior, failure/error behavior for a bad device_id, or whether status is live vs cached.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences contain only useful information: what the tool returns and the required prerequisite. The action and scope are front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter read tool with no output schema, the description covers what will be returned and how to get the parameter. It omits edge-case behavior, but the essential calling context is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, giving baseline 3. The description adds value beyond the schema by telling the agent where to obtain device_id and that it must be obtained first from list_locks.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description starts with a specific verb ('Get'), names the exact resource ('current status ... for one lock'), and enumerates the returned fields (status, battery level, model/serial). This clearly differentiates it from sibling tools like list_locks (listing) and lock_door/unlock_door (actuation).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives a clear prerequisite and data source: 'Get device_id from list_locks first.' It does not explicitly say when not to use this tool, but the operation is unique among the siblings, so the workflow instruction is the main needed guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_locksA
List every Kwikset lock on the account, with its current status, battery percentage, and the home it belongs to. Call this first to find a lock's device_id before locking/unlocking/checking it.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description must disclose behavior. It clearly states what the tool returns (locks with status, battery, and home) and implies that the device_id is obtainable from the result, which is essential for subsequent operations. It does not explicitly mention side effects (likely read-only) or edge cases, but for a simple listing tool this is sufficient.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two concise sentences, with the core purpose and returned content front-loaded and the usage guidance provided second. There is no wasted wording or off-topic detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description adequately covers what the tool does, what it returns, and when to use it relative to siblings. It does not mention pagination or ordering, but given the simple nature of the tool and absence of parameters or output schema, this is a minor gap and does not hinder correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so no parameter documentation is needed. The baseline of 4 for zero-parameter tools applies because there is nothing to explain, and the description does not omit any required parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('List') and a specific resource ('every Kwikset lock on the account'), and enumerates the returned fields (status, battery, home). This clearly distinguishes it from sibling tools that target individual locks (lock_door, unlock_door, get_lock_status).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to use this tool: 'Call this first to find a lock's device_id before locking/unlocking/checking it.' This gives a direct and clear usage directive that routes the agent to this tool before the sibling operations, and implies when not to use it (after you have the device_id).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lock_doorA
Lock a Kwikset door. Get device_id from list_locks first.
| Name | Required | Description | Default |
|---|---|---|---|
| device_id | Yes | A lock's device_id, from list_locks |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden for behavioral disclosure. It states the action but does not mention side effects beyond locking, required permissions, failure modes, idempotency, or what response to expect.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences front-load the action and then give the essential prerequisite. There is no filler or redundant wording.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple with one well-documented parameter and a clear action, so the description is minimally viable. However, with no annotations and no output schema, it leaves return/error behavior and side-effect expectations unexplained.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already says device_id comes from list_locks. The description repeats that prerequisite but adds no additional parameter semantics beyond what the schema provides, so the baseline score applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource ('Lock a Kwikset door') and the prerequisite ('Get device_id from list_locks first') further clarifies its role. It is clearly distinct from unlock_door and list_locks without needing to read their schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives a clear usage context by instructing the agent to obtain device_id from list_locks first. It does not explicitly mention when not to use it or name unlock_door as the alternative, but the command's action is self-explanatory enough for basic routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
unlock_doorA
Unlock a Kwikset door. This is a physical-security-sensitive action - only call this after the user has explicitly asked to unlock this specific door, and pass confirm=true. Calling with confirm=false (the default) is a no-op that returns a reminder instead of unlocking.
| Name | Required | Description | Default |
|---|---|---|---|
| confirm | No | Must be true to actually unlock the door. | |
| device_id | Yes | A lock's device_id, from list_locks |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full responsibility, and it delivers. It flags the action as physical-security-sensitive, states the confirmation requirement, and discloses that calling with confirm=false is a no-op that returns a reminder instead of unlocking.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with no filler. It front-loads the core action, then packs the security warning, caller precondition, and confirmation behavior into compact, high-value language.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter physical-security action with no output schema, the description covers everything needed to invoke it correctly: the requirement for explicit user consent, the needed confirmation flag, and the no-op fallback. Nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so parameters are already documented. The description adds meaningful behavioral context beyond the schema by specifying that confirm=true is required and that false leads to a no-op reminder, which is not fully captured in the schema alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Unlock a Kwikset door.' It is immediately distinguishable from sibling tools like lock_door and list_locks, leaving no ambiguity about the tool's function.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: only after the user has explicitly asked to unlock this specific door. It also requires confirm=true and explains that confirm=false is a deliberate no-op, providing clear behavior boundaries.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
debug_raw_devices - First observed
get_lock_status - First observed
list_locks - First observed
lock_door - First observed
unlock_door
TDQS
Scored across 5 tools
Each tool has a crystal-clear, non-overlapping purpose: listing all locks, fetching one lock's status, locking, unlocking, and debugging raw API data. No two tools could be confused for each other.
All tools follow a consistent verb_noun pattern (list_locks, get_lock_status, lock_door, unlock_door, debug_raw_devices). The naming is uniform and predictable.
Five tools is perfectly scoped for a smart-lock management server. Each tool addresses an essential core function, with no redundancy and no obvious missing high-level operation.
The core lifecycle for lock management is fully covered: list, get status, lock, and unlock. The debug tool covers a diagnostic edge case, making the surface feel complete for its stated purpose.
Maintenance
Related MCP Connectors
- mytesla.ioOAuthio.mytesla
Control your Tesla from your AI assistant - climate, charging, access, and security.
- platform7nOAuthtech.p7n
Connect Claude to your Platform7n workspaces — chat, links, and tasks. One-click OAuth.
WHOOP recovery, strain, sleep and workouts in Claude via official WHOOP OAuth. Free, open source.
Run UX research from Claude — create card sort studies, list studies, pull headline stats.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables control of smart locks through the Seam API, allowing users to lock/unlock doors, check status, and manage access codes across 100+ supported lock brands. Supports comprehensive access code management including temporary codes and multi-lock operations.-
- FlicenseNot gradedqualityDmaintenanceEnables Claude Desktop to control Lutron Caseta smart lighting systems locally, including turning lights on/off, setting dimmer levels, and activating scenes.-
- AlicenseNot gradedqualityDmaintenanceEnables Claude Desktop to read and control Home Assistant devices via natural language, with configurable safety restrictions on sensitive actions.2MIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude to control PetLibro RFID pet feeders and water fountains by checking food/battery/water status, dispensing food by the cup, and force-opening feeder lids via the PetLibro cloud API.GPL 3.0