Four Servers, Four Tokens, Zero Idea What Broke
Here is a Tuesday night in the average home lab that has gone all in on MCP. Proxmox has a token. Home Assistant has a long lived access token. NetAlertX has its own API key. Portainer has another. Each one lives in its own .env file, scoped to exactly one service, because that is the pattern every MCP writeup this year has told you to build. Narrow server, narrow token, done.
Then something breaks at 2 AM. A container will not restart, a VM is pegged at 100 percent, and you are asking your agent to help you find out why. Except the agent’s Proxmox view cannot see the Portainer logs, the Portainer view cannot see the network scan that would tell you a device just joined the LAN and started hammering a service, and none of the four tokens has ever been rotated because rotating four separate credentials in four separate places is exactly the kind of chore that gets pushed to “later.” You have four narrow blast radii and zero unified view of what actually happened. You are debugging with one eye covered, then the other, then the first one again.
That is the problem a project called Skeleton Key MCP set out to fix: one MCP server, one OAuth consent, one scoped Vaultwarden collection holding every credential in your lab. It is the right shape for the problem. Credential sprawl goes away, there is a single audit trail instead of four partial ones, and you stop running the same consent dance four times because you are running it once. It is also three months old, sitting at one star on GitHub, with no tagged release and no license file. Both of those facts are true at the same time, and the rest of this article is about holding them there without flinching from either.
The Case for Four Tiny Blast Radii
Before selling you on the one big vault, give the current pattern its due, because it wins on the one axis that matters most when something goes wrong: blast radius.
If your Proxmox MCP server’s token leaks, or your agent goes sideways and starts issuing commands it should not, the damage is contained to Proxmox. Your Home Assistant token still just points at Home Assistant. Your NetAlertX key still just reads network presence data. A compromised agent session with a narrow token can only do narrow damage, and that is a real, defensible security property. Four separate credentials mean four separate doors, and breaking down one door does not hand anyone the other three keys.
A single aggregator inverts that math on purpose. One compromised session, in the worst case, has a path to everything the vault knows about: your Proxmox nodes, your Home Assistant instance, your UniFi controller, any host you have handed an SSH key to. Do not wave that away with “well, it’s scoped.” That is the actual tradeoff you make the moment you stand up something like Skeleton Key. Keep that number in your head for the rest of this piece, because everything Skeleton Key does past this point is really an argument about how to make that single point of failure survivable.
What Skeleton Key Actually Does
Skeleton Key MCP (sjdodge123/skeleton-key-mcp) is a TypeScript MCP server that fronts a scoped Vaultwarden vault and a set of connectors to homelab services. Adapted from the README’s minimal stack, with the bridged networking public URL the repo calls out separately folded in:
services: skeleton-key: image: ghcr.io/sjdodge123/skeleton-key-mcp:latest container_name: skeleton-key restart: unless-stopped ports: - "192.168.1.10:8787:8787" # bind your host's LAN IP environment: SKELETON_KEY_PORT: "8787" SKELETON_KEY_BIND_HOST: "0.0.0.0" SKELETON_KEY_PUBLIC_URL: "https://192.168.1.10:8787" # on bridged networking, set this or the TLS cert misses your LAN IP # SKELETON_KEY_DISABLE_EXECUTE: "1" # optional read-only mode volumes: - skeleton-key-data:/datavolumes: skeleton-key-data:The vault side is where the design earns its keep. You do not point Skeleton Key at your personal Bitwarden vault. The first run wizard walks you through creating a dedicated Vaultwarden organization, a collection inside it, and a service account user that belongs to that one org and that one collection. Bitwarden’s key model keeps organization keys separate from your personal user key, so that service account is cryptographically unable to decrypt your own vault even if someone fully compromises it. The collection boundary inside that org is a different kind of fence, though: every collection in a Bitwarden organization is encrypted under the same org key, and which collections a member can open is a server side access check rather than a cryptographic one. Give the homelab org exactly one collection and that distinction stops mattering.
Reads come off the bw CLI’s local encrypted offline cache, which sounds like a footnote until your Vaultwarden instance has a bad night. Instead of every credential lookup failing closed, Skeleton Key degrades to last known good values from the cache. And when a target is registered, it pins the vault item’s immutable id, not its display name. A deploy time credential read re-syncs the cache first, so renaming an item mid-migration cannot accidentally serve a stale value, and deleting an item never quietly satisfies a reference that should have failed loud.
On top of the vault sits a tool tier system. Every tool Skeleton Key exposes is tagged read or execute. Read tools just answer questions: list credentials, list registered targets, tail a log. Execute tools change state, and each one produces a precise confirmation string that surfaces to Claude’s own permission prompt before it runs, then gets written to an append only audit log. Server side refusals land there too, so a container running with SKELETON_KEY_DISABLE_EXECUTE=1 leaves a record of every execute tool it turned away. Destructive shell patterns like rm -rf, mkfs, and dd are refused by policy even if you say yes, which is a sane guardrail no narrow single service token gives you for free.
The last piece is the one that actually makes the whole design defensible: Skeleton Key never pushes its own SSH key onto anything. When you ask it to set up access to a new host, it generates a keypair, stores the private half in the vault, and hands you back the public key. You install that key on the target machine yourself. That manual step is not friction the project forgot to automate. It is the security boundary, stated plainly in the README, and it means the server cannot silently expand its own reach without you doing something on the machine in question.
The Connector Table (and the Escape Hatch)
Here is what actually talks to what, straight from the README:
| Type | Status | Notable tools |
|---|---|---|
ssh | read + gated execute | tail_log, journalctl, run_readonly, run_command, restart_service, with a sudoPrefixes option for hosts like Synology where the docker socket needs root |
http | generic | get (read), request (execute) |
portainer | read + gated execute | list/inspect containers and stacks, start/stop/restart, create_stack, update_stack |
home-assistant | read + gated execute | ha_states, ha_logbook, ha_call_service, ha_backup |
proxmox | read + gated execute | list_nodes, list_guests, node_status, guest_power |
unifi | read + gated execute | list_devices, list_clients, set_network_ipv6, force_provision |
synology, pihole | planned | none yet |
Two rows do the heavy lifting here: ssh and http are both generic connectors, not dedicated integrations. That matters more than the named connector list does. If Skeleton Key has not shipped a Synology or Pi-hole connector yet, that is fine, because anything with a shell you can reach over SSH or an API you can reach over HTTP is already usable today. You are not stuck waiting on a roadmap item to talk to some oddball service you’re running. The generic connectors are the actual escape hatch, and they cover more of a typical home lab than the named list suggests.
Onboarding: Wizard, Then a Conversation
The setup wizard handles the parts that are tedious to do by hand: set a master passphrase that encrypts Skeleton Key’s own secrets and doubles as your admin login, connect the scoped Vaultwarden org and collection, review the automatic scoping checks, optionally scan the LAN and register what it finds, enroll TOTP, optionally turn on boot auto-unlock, and copy the claude mcp add command for your client.
claude mcp add --transport http skeleton-key https://192.168.1.10:8787/mcpThat first request 401s with a discovery hint, Claude auto-registers itself, and Skeleton Key serves an “Authorize an AI agent” page that you approve with a TOTP code, PKCE, short-lived tokens, the works. There is still a static bearer token available under “Advanced” in the wizard, for clients that cannot do OAuth, but it is the fallback rather than the path the setup steers you down.
Past that point, onboarding stops being a wizard and turns into a conversation. The README walks through the real shape of it: you ask Claude to map the network, and it runs network_scan and reports what it found. You ask it to set up SSH access to one of those hosts, and it runs vault_generate_ssh_key, stores the private key, and hands you the public key to install. You install that key on the host yourself, by hand, the one deliberately non-automated step in the whole flow. Then you ask it to confirm access, it runs vault_validate_ssh, SSHes in with the stored key, runs id, and reports the uid= line back to you. Four turns of conversation, and you have a registered target with a credential nobody ever pasted into a chat window.
The Stuff That Should Make You Pause
Now the parts that should slow you down. Skeleton Key generates a self-signed TLS certificate into /data/tls on first boot, so every client trusts it once on first connect. That is workable, but it is trust on first use, and you should actually verify the fingerprint rather than blindly curl -k your way past a warning. The container prints it in the boot log:
docker logs skeleton-key | grep fingerprintCompare that against what your client shows you before you accept it. For Claude Code specifically, point it at the cert explicitly instead of disabling verification:
export NODE_EXTRA_CA_CERTS=/path/to/skeleton-key.pemPast the cert, the project itself is young. It was created in July 2026, so it is about three months old as of this writing. It has one star, no forks, and six open issues. There are no tagged releases and no git tags anywhere in the repo. The CI workflow is wired for semver tags whenever the maintainer cuts one, and in the meantime it stamps every push to main with an immutable short-sha tag, 53 of them on GHCR as of September 2026. So you can pin:
docker pull ghcr.io/sjdodge123/skeleton-key-mcp:sha-3947122What you cannot get is any signal about which of those 53 commits is a safe place to stop. A release tag is a maintainer saying “this one works.” A sha tag is just a checksum of whatever got pushed that afternoon.
The one that should actually change your risk calculus is this: there is no LICENSE file in the repo, and no license set in the GitHub metadata either. That is not a formality. Without a license, default copyright applies, and there is no explicit grant of permission to use, modify, or redistribute the code, whatever the README’s tone implies about being open for homelab use. Treat that as a real constraint.
Add the operational constraint the README states plainly: LAN only, never internet exposed. Remote access means VPN into your network first, no exceptions. That is a hard requirement for a server that, by design, can reach every credential and every host in your lab.
Which brings the blast radius conversation back around. Yes, this single server can reach everything if you register everything with it. That is the tradeoff you signed up for in exchange for the unified audit trail and the one consent flow. The controls you actually lean on to make that survivable are the OAuth approval gate on every newly registered client and the SKELETON_KEY_DISABLE_EXECUTE=1 kill switch that turns every execute tool into an audited refusal while leaving reads intact. Those two levers are doing real work.
How to Try This Without Betting the Lab
You don’t need to hand this thing your whole lab on day one. A smaller first step:
- Deploy it with
SKELETON_KEY_DISABLE_EXECUTE: "1"set from the start, so every tool that changes state gets refused and logged, and you get a feel for the read side risk free. - Register exactly one low stakes target, something like a Pi-hole box or a test VM, not the Proxmox host running your entire lab.
- Bind the container to a single LAN IP in the compose port mapping, the way the shipped example already does, rather than leaving it reachable from anywhere on the subnet.
- Back up
SKELETON_KEY_DATA_DIRbefore you do any of this. It is the one stateful part of the whole setup: the encrypted bootstrap store, thebwoffline cache, the audit database, andtargets.yamlall live there, and it is your only way back if something goes sideways.
Once read-only mode has run for a couple of weeks without surprises, flip execute on for that one target and watch the audit log for a while before you add a second.
The Verdict
Copy the pattern today. One vault, one consent flow, one audit trail beats four scattered tokens and no way to see across them when you’re debugging at 2 AM, and that argument does not depend on any one implementation being production ready. Run the container itself only if you are fine being an early adopter on a three month old, one star, unlicensed side project where no commit has ever been blessed as a release. If you do run it, run it read only against a couple of low stakes targets and make it earn execute access one target at a time. It has not earned the run of your whole lab yet, and neither has anything else this new.
Common Questions
Do I need Vaultwarden to run Skeleton Key MCP?
Yes. Skeleton Key’s security model depends on Vaultwarden’s organization and collection structure, where a service account’s org key is cryptographically separate from your personal vault key. The first run wizard does not create that org for you. It tells you which org, collection, and service account user to make, then connects and runs its scoping checks.
What happens if Vaultwarden goes down while Skeleton Key is running?
Credential reads keep working. Skeleton Key serves them from the bw CLI’s local encrypted offline cache, so a Vaultwarden outage degrades you to last known good credentials instead of locking every registered target out. Vault writes and new credential requests still need Vaultwarden reachable.
Can I run Skeleton Key alongside the per-service MCP servers I already have?
Yes, nothing stops you running both patterns at once. A common path is registering one or two low stakes targets in Skeleton Key while your existing narrow-token servers keep handling the services you already trust, then migrating targets over as Skeleton Key proves itself.
What does the missing license actually mean for me?
It means no explicit grant to use, modify, or redistribute the code. The repo has no LICENSE file and no license set in its GitHub metadata, so default copyright applies. Running the published container is a personal risk call until the maintainer adds a license, whatever the README’s tone suggests about open homelab use.