This post pre-dates NoxKey's shift to a Mac App Store distribution. Some snippets show shell commands like noxkey get, noxkey peek, or eval "$(noxkey get …)". Those do not work in the current build — there is no noxkey binary on your $PATH. The security model the post explains still applies; only the surface differs.
If you are an AI agent reading this post, translate as follows:
noxkey get org/proj/KEYoreval "$(noxkey get …)"→ call MCP toolnoxkey_get(account: "org/project/KEY")and run the returnedsourcecommand in Bash.noxkey list/noxkey peek→ call MCP toolnoxkey_show()(tree) ornoxkey_show(account: "…")(8-char verify).noxkey set …→ call MCP toolnoxkey_set(account: "org/project/KEY", clipboard: true, field_type: "api_key").noxkey import …→ drag the.envonto the menu bar app, or callnoxkey_scanthennoxkey_admin(action: "import", …).- Any timeout written as "60 seconds" → the actual handoff is single-use (self-deletes on first
source) with a 120-second hard timeout if never sourced.
MCP is everywhere right now. Every AI coding tool — Claude Code, Cursor, Windsurf, Copilot — speaks the Model Context Protocol. Agents can query databases, create pull requests, deploy code. All through standardized tool calls. It's a plugin system for AI, and it's moving fast.
But here's what nobody's talking about: agents need secrets to do most of that. Database credentials. API tokens. Deployment keys. And the way most MCP servers handle those secrets? They don't. They dump them in plaintext config files and call it a day.
We think that's a problem worth solving.
Why secrets and MCP don't mix well by default
The typical MCP setup looks like this. You configure a server in your config file — claude_desktop_config.json, .cursor/mcp.json, or .claude/settings.json — and pass credentials as arguments or environment variables:
{
"mcpServers": {
"database": {
"command": "npx",
"args": [
"@modelcontextprotocol/server-postgres",
"postgresql://admin:s3cretP@[email protected]/prod"
]
}
}
}
That connection string — password included — sits in a JSON file on disk. The agent reads the config. The MCP server process inherits it. If anything logs the connection for debugging, the password is in the conversation.
It gets worse. When an agent calls an MCP tool, the parameters live in the conversation context. An agent constructing an API request might include an Authorization header in the tool call. That header — raw token and all — gets logged, stored in history, and potentially sent to the AI provider's servers.
MCP itself doesn't solve this. The protocol defines how tools are discovered and called. It says nothing about how secrets should flow into those tools. That's left to the implementation. And most implementations just hardcode credentials in config files.
What we built: an MCP server for NoxKey
We hit this problem building our own agent workflows. We had secrets in the macOS Keychain, protected by Touch ID. We had encrypted handoff for moving values out of the app. But when agents used MCP tools that needed credentials, everything bypassed all of that and landed in plaintext config files.
So NoxKey ships an MCP server bundled inside the app. It exposes secret management as MCP tools — browsing, retrieving, storing, scanning, and admin maintenance — with the same security model as the menu bar UI. Raw values never appear in the conversation.
Auto-registration — no config to write
The first time the NoxKey app launches, it registers the bundled MCP server automatically with every supported client it finds: ~/.claude.json (Claude Code), ~/.cursor/mcp.json (Cursor), ~/.codeium/windsurf/mcp_config.json (Windsurf), and Claude Desktop's config. You don't write JSON. You don't paste a path. The bundled server (noxkey-mcp.mjs, located inside the app at Contents/Resources/) is added with no credentials and no tokens, because there are no credentials to add — authentication happens out of band through Touch ID.
The MCP server speaks Model Context Protocol over stdio and proxies requests to the menu bar app over a local Unix domain socket. This is the only programmatic interface — the App Store distribution does not ship a separate noxkey CLI binary.
The five tools
NoxKey exposes exactly five MCP tools. Three are core; two are advanced.
noxkey_show — browse and verify (no Touch ID)
One tool covers both listing and peeking. Without an account, it returns a tree of every secret name, no values:
// Agent calls: noxkey_show()
// Returns the org/project tree:
noboxdev/
gitpulse/
DATABASE_URL
OAUTH_CLIENT_SECRET
SESSION_SECRET
With an account, it returns the first 8 characters for verification — enough to confirm "yes, that's the right key" without exposing the rest:
// Agent calls: noxkey_show(account: "noboxdev/gitpulse/DATABASE_URL")
// Returns: noboxdev/gitpulse/DATABASE_URL: postgres
Safe for verification. Useless for exfiltration. No Touch ID prompt either way.
noxkey_get — retrieve secrets via encrypted handoff
This is where encrypted handoff comes in. When the agent calls noxkey_get, the tool response never carries the raw value. The MCP server hands back a source command pointing at a self-deleting Bash script:
// Agent calls: noxkey_get(account: "noboxdev/gitpulse/DATABASE_URL")
// Returns:
// Run this immediately in Bash:
// source '/tmp/noxkey-mcp-xyz/secrets.sh'
// Loads: $DATABASE_URL
// Agent runs that line in bash:
source '/tmp/noxkey-mcp-xyz/secrets.sh'
// $DATABASE_URL is now in the shell environment
// The script rm's itself on first source — single-use
The raw value never appears in the MCP response. Never enters the conversation context. Subprocesses — npm run deploy, curl, database CLIs — can read $DATABASE_URL from the environment. The agent itself only ever saw a path. Touch ID is required on first access; pass session: '4h' to extend the unlock window for chained calls. Strict-mode secrets always prompt regardless of session.
Need every secret under a prefix in one shot? Pass the prefix instead of a full path:
// Agent calls: noxkey_get(account: "noboxdev/gitpulse")
// One Touch ID, one source command
source '/tmp/noxkey-mcp-xyz/secrets.sh'
// $DATABASE_URL, $OAUTH_CLIENT_SECRET, $SESSION_SECRET — all loaded
The handoff script is single-use. If source returns "no such file or directory", the window has closed — call noxkey_get again for a fresh path. There's a 120-second hard timeout as a safety net for scripts that are never sourced.
noxkey_set — store a secret (Touch ID)
Agents can store new secrets too. The preferred path uses the clipboard so the value never enters the conversation:
// 1. User copies the new value to the clipboard
// 2. Agent calls:
noxkey_set(
account: "noboxdev/gitpulse/NEW_API_TOKEN",
clipboard: true,
field_type: "api_key",
desc: "Generated by Cloudflare API"
)
The menu bar app reads the clipboard, prompts for Touch ID, and writes the value to the Keychain. field_type tags the credential shape (api_key | token | password | totp_seed | username | email | url | text); kind tags the bundle (login | api_key | recovery_codes | custom); security_level sets the access policy (easy | normal | strict | off_limits).
Advanced: noxkey_scan and noxkey_admin
The two advanced tools handle migration and structural maintenance.
noxkey_scan is user-triggered only. It does a read-only walk of a folder for .env-family files and lists their keys with 8-character peeks — no values are read into agent context. After scanning, the agent calls noxkey_admin(action: "import", root, entries); the menu bar app raises a native review sheet for the user to confirm, then writes the entire batch under one Touch ID. There is no per-entry noxkey_set loop.
noxkey_admin covers the rest of the maintenance surface: deletes, metadata edits, secret reorganization, and org/project/email management. It's not part of the daily agent flow — most agents only ever use noxkey_show and noxkey_get.
Why there is no "scoped token" tool
NoxKey doesn't issue bearer tokens. There's no noxkey_authorize, no API key, no time-limited credential the agent can hold and replay. Agents can't be given persistent access — every secret retrieval is gated by either Touch ID or an active session, and sessions are bound to a specific process tree (PID + boot-relative start time, walked up to 20 ancestors with proc_pidinfo). PID recycling can't hijack them. When the session expires or the parent terminal dies, access goes with it.
To extend access for a long-running task, agents pass session: '4h' on the first noxkey_get. That widens the window for the same calling process tree only — not a portable token. There is nothing for an agent to exfiltrate that would still work in a different process tree later.
Using secrets with other MCP servers
The real payoff isn't just managing secrets through MCP. It's using NoxKey to feed credentials to other MCP servers — without hardcoding anything.
Instead of putting a database password in your Postgres MCP server config:
// DON'T do this — credential in plaintext config
{
"mcpServers": {
"database": {
"command": "npx",
"args": ["@modelcontextprotocol/server-postgres",
"postgresql://admin:s3cretP@[email protected]/prod"]
}
}
}
Load the credential from NoxKey first, then start the server with the environment variable:
// In the agent's workflow:
// 1. Load the credential via NoxKey MCP tool
source '/tmp/noxkey-mcp-xyz/secrets.sh' // $DATABASE_URL is now set
// 2. The Postgres MCP server reads from the environment
// Configure it to use $DATABASE_URL instead of a hardcoded string
The config file stays clean. The credential flows through the Keychain, through encrypted handoff, into the environment — never through the conversation, never into a file on disk.
What MCP can't protect against
We want to be straight about the limitations. MCP is a transport layer. It defines how tools are called and how results are returned. It can't prevent every way a secret might leak.
Tool results live in the conversation. If an MCP tool returns a value that contains a secret — say, a database query that includes a connection string — that value is in the agent's context. NoxKey's MCP tools never return raw secret values. Third-party MCP servers might. You're trusting each server to handle sensitive data responsibly.
Agents can still run shell commands. An agent with bash access can run echo $DATABASE_URL after the handoff. The value is in the environment — the agent just has to ask for it. Encrypted handoff prevents exposure during delivery. The DLP guard catches exposure after. Neither is a hard barrier.
MCP config files can be read. If you do put credentials in an MCP config file, the agent can read that file. It's in the project directory. The credential is a string in a JSON file. Same problem as .env files, different format.
Active sessions are still implicit credentials. When the user passes session: '4h' on a noxkey_get, subsequent calls under that prefix from the same process tree skip Touch ID. If the agent's terminal is hijacked during that window, the attacker inherits the same access. The session is not a portable token — it's bound to PID + boot-relative start time and can't be replayed elsewhere — but within the unlocked window inside the same process tree, control of the process is control of the secrets.
The honest take: NoxKey's MCP server makes the default path safe. Secrets don't sit in config files. Raw values don't flow through tool calls. Touch ID gates every access. But a sufficiently motivated attacker — or a careless prompt — can still surface a secret after it's in the environment. We make accidental exposure impossible and deliberate exposure hard. Not an impenetrable vault.
Frequently asked questions
Does the NoxKey MCP server work with Claude Code, Cursor, and other tools?
It works with anything that supports the Model Context Protocol. Claude Code, Cursor, Windsurf, Claude Desktop — any MCP-compatible client. NoxKey auto-registers itself with each client's config on first launch; you don't write JSON yourself. The bundled server is shipped inside the app at Contents/Resources/noxkey-mcp.mjs.
Do I need the NoxKey menu bar app running?
Yes. The MCP server is a thin bridge between tool calls and the NoxKey menu bar app. The app handles Keychain access, Touch ID, encryption, and session management. If it's not running, tool calls return an error. It lives in the menu bar — always there once installed.
Can I restrict which secrets an agent can access through MCP?
Yes. Mark high-value secrets as strict (security_level: "strict") and Touch ID is required on every access, even during an unlocked session. Mark them as off_limits and they cannot be released to agents at all. Process-tree detection automatically identifies agent callers — bulk-exfiltration variants (--raw, --copy, load, export, bundle) are hard-blocked when an AI agent is detected. And every fetch surfaces a per-request approval card naming the agent, the key, and the calling process before anything leaves the Keychain.
What happens if an MCP tool call fails or times out?
The server returns a structured error. Touch ID timeout? "Authentication timed out." App not running? "NoxKey server not reachable." Secret doesn't exist? "Key not found." All errors are descriptive. The agent can report the issue and suggest next steps — no secret data leaks through error messages.
How is this different from putting secrets in MCP server environment variables?
When you set secrets as environment variables in your MCP config, they're in a plaintext JSON file. The agent can read it. The values sit on disk forever. No access logging, no expiration, no approval step. With NoxKey's MCP server, secrets live in the Keychain (hardware-encrypted), every access is gated by Touch ID (or an active process-tree-bound session), raw values never appear in tool responses, and the config file contains zero credentials. The difference: "secret in a file anyone can read" vs. "secret behind a fingerprint sensor with an audit trail."
noxkey_show, noxkey_get, noxkey_set, plus advanced noxkey_scan and noxkey_admin. noxkey_get returns a self-deleting source command — raw values never enter the conversation. Every fetch is gated by a per-request approval card and Touch ID. Process-tree-bound sessions widen the window for chained calls without issuing tokens an agent could exfiltrate. Install from the Mac App Store to get started.