A connector can now reference secrets the user keeps in the OS keystore instead
of shipping them through host config files; install accepts .zip sources and
installs a fetched connector's dependencies; two more content surfaces (droid
status line, junie skills); every registered host has a verification lane.
Highlights — secrets that never touch a host config
Every MCP server needs a credential, and until now every connector author solved
the same problem alone: bake the key into a host's JSON config, ask users to
export environment variables, or write keychain glue for each operating system.
0.7.0 makes that the framework's job.
One line for the author. Reference the secret where the env var would be:
server: {
transport: "stdio",
command: "npx",
args: ["-y", "@acme/db-mcp"],
env: { ACME_API_KEY: "${secret:api-key}" },
}One command for the user. acme-db secrets set api-key asks for the value
with a hidden prompt (or reads stdin) and stores it in the operating system's
keystore: macOS Keychain, Linux Secret Service, Windows Credential Manager. An
opt-in plaintext file store covers headless machines.
Nothing in between. Host config files carry only the serve wrapper's
placeholder (--secret-env ACME_API_KEY={secret:api-key}, a form no host
expands). At launch the wrapper reads the keystore and injects the value into
the server process alone; the value never appears in argv, in a host file, in
the names index, in telemetry or in any CLI output. A missing or empty secret
stops the launch with the exact secrets set command instead of starting the
server with an empty key, and each connector id is its own keystore namespace.
Day two is covered. install warns per unset name, doctor reports
<id>: secrets, secrets list and secrets check show state without values,
and MCPB bundles and registry server.json declare the secrets for hosts that
install the package themselves.
And it is an SDK, not only a CLI. openSecretStore({ connectorId }) gives a
connector's own tooling the same store — get, set, delete, list,
availability, selfTest — with findSecretRefs, resolveSecretBackendId,
SECRET_BACKEND_IDS, SecretError and SecretResolutionError exported from
the package root:
import { openSecretStore } from "@ken-jo/agent-connector";
const store = openSecretStore({ connectorId: "acme-db" });
store.set("api-key", value); // the value is never logged or printed
store.has("api-key"); // true
store.list(); // [{ name, backend, present, updatedAt }]Added
- OS-keystore secrets —
${secret:NAME}(#341). A stdio server writes
"${secret:NAME}"as a value in itsenv(baseserver.envor a
platforms[<id>].server.envoverride) and the user stores the value once with
secrets set NAME.defineConnectormoves such entries into the new
ServerDef.secretEnv, rejects the reference in every other server field
(command,args,cwd,url,headers,auth) and on remote servers
(an override is judged by the transport it inherits), and leaves a
reference-free server byte-identical. Hosts see only theservewrapper's
--secret-env NAME={secret:NAME}placeholder; at launch the wrapper reads the
keystore, expands any${env:VAR}around a reference, injects the value into
the real server's environment only, and refuses to start when a name is unset
or empty (SecretResolutionErrorwith thesecrets sethint). A server with
secrets is wrapped even when telemetry is off (measurement disabled, judged by
the host's effective server);doctor --proberesolves the same way. Backends:
keychain(macOS,/usr/bin/security, value as hex on stdin),secret-service
(Linux,secret-toolover D-Bus, value on stdin, 8192-byte cap),
credential-manager(Windows, PowerShell under%SystemRoot%calling
CredRead/Write/DeleteW, value in the child environment, 2560-byte cap) and
the opt-in plaintextfilestore (<dataRoot>/secrets/file-store.json,
0700/0600). Selection:--backend, elseAGENT_CONNECTOR_SECRETS_BACKEND,
else the OS-native backend; a names-only index records which backend holds
each name, and each connector id is its own keystore service. secrets set | delete | list | check(#341): the value comes from a hidden
TTY prompt or stdin (no--valueflag) and is never printed;listshows
name backend present updated;checkreports availability and runs a
write → read → delete self-test; usage errors exit 2, keystore failures 1.
Branded CLIs (createConnectorCli) auto-scope it to their connector.
installwarns per unset name (and per unset, defaultless${env:VAR}
inside a secret-bearing value), and when a connector id is re-registered from
another module while secrets are stored under it;doctoradds the framework
check<id>: secrets. MCPBuser_configgets one sensitive, required field
per referenced name substituted into the env template; registryserver.json
declares the env var as a secret, requiredenvironmentVariablesentry.- SDK (#341):
openSecretStore,findSecretRefs,resolveSecretBackendId,
SECRET_BACKEND_IDS,SecretError,SecretResolutionErrorand the store
types are exported from the package root. Docs: README,llms-full.txt
(§2.2,### secrets, §9.1), the site CLI reference and Operate guide §6, the
authoring skill reference. .zipconnector sources (#337):install <path-or-URL>.zipextracts with
unzipor a zip-capable bsdtar (macOS / Windows), ignores__MACOSX, and
flattens a single top-level directory; a direct.zipURL no longer falls
into the git-clone path. Closes #264.- Dependencies of a fetched connector (#337): when a fetched connector's
package.jsondeclares dependencies itsnode_modulesdoes not satisfy,
installrunsnpm install --ignore-scripts --omit=devthere (vianpm.cmd
on Windows) before the package gate loads the config. Refs #250. - droid status line (#338):
statusLine = { command, maxRows? }in
~/.factory/settings.json(user) or<projectDir>/.factory/settings.json
(project), through the config-patch ownership ledger (set-if-absent, drift is
warned, first writer wins, removed only when last owner and unchanged). The
runtime parser maps no host fields — it returns{ host, capabilities, raw }
only. Status line hosts: 3 → 4. Closes #266. - junie Agent Skills (#340):
<projectDir>/.junie/skills/<name>/SKILL.md
and~/.junie/skills/<name>/with frontmattername/descriptionand
supporting resources. Skills hosts: 35 → 36. Closes #267.
Changed
- Package-metadata context across framework copies (#337): the
AsyncLocalStoragebehindwithConnectorPackageMetadatalives on
globalThisunderSymbol.for("@ken-jo/agent-connector:package-metadata-context"),
so a fetched connector importing its ownnode_modules/@ken-jo/agent-connector
copy still sees the installing copy's metadata. Takes effect once both copies
carry this release. - Shared owned-JSON-leaf helpers (#338):
BaseAdapter.installOwnedJsonLeaf
/uninstallOwnedJsonLeafandsrc/core/json-leaf.ts; qwen-code and
antigravity-cli delegate to them, behavior unchanged. - README links the Troubleshooting page from the CLI table (#336). Closes #263.
Verified
- 42 of 42 registered hosts have a
verify-hostlane (#339): cline
(placement-only — the CLI has no offline accept verb;dirspre-creates
~/.clineso the CLI settings mirror is exercised) and open-interpreter
(live-accept at user scope;acceptScopesmarks the project scope, which the
CLI does not read, as placement-only). Closes #268. - Keystore backends (#341): the macOS keychain round trip (
security -i
write,-gread, update, delete, unicode and multi-line values) runs against a
throwaway keychain intests/core/secrets.test.ts; the Linux and Windows
backends are pinned by exec-contract tests (argv never carries a value), and
CI's windows-smoke job runs them.
Full changelog: v0.6.5...v0.7.0