Repository navigation
mcp run loads no .env: RFC #50 §3.1 promised loadEnv, so operator-credential consumers must keep wrapper scripts #58
Description
Package: agent-bundle (mcp run), at main da5df1d (PR #52, RFC #50 Phase 1). Found by the movie-library Phase-2 migration onto that commit.
What we tried
Replace the consumer's bash launcher wholesale with agent-bundle mcp run --server <id> --target portable --artifact artifact, as RFC #50 §3.1 specifies:
agent-bundle mcp run --server <name> [--target <t>]— the missing foreground runner: resolve the (content-hashed) entry from the artifact manifest, apply.envloading (rsbuild'sloadEnv, ascreateRslibdoes at createRslib.ts#L76-L81), exec node. This deletes the resolve-and-exec core ofrun-stdio.sh.
The shipped runner resolves and execs, but the .env half never landed: runMcpForeground (packages/agent-bundle/src/services/mcp-run.ts) spawns with exactly
env: { ...inheritedEnv, ...launch.env }, // process.env + expanded manifest envand there are zero loadEnv references anywhere in packages/agent-bundle/src at da5df1d (checked with git grep).
Why it matters
Real plugins keep operator configuration in a plugin-root .env: movie-library's carries tracker session cookies, the seedbox SSH alias, and state-directory pins. A bare mcp run boots the server with none of it — servers come up credential-less. So the launcher class the RFC set out to delete survives: movie-library's run-stdio.sh shrank from 95 to 57 lines (the resolve-and-exec core is gone) but stays, purely to source .env and pin state env before delegating to mcp run. The RFC even predicted this in its migration table — "a thin alias may keep the .env/state-dir operator ergonomics" — this issue asks for the runner to own that seam instead of every consumer keeping an alias.
Repro sketch
echo 'MY_TRACKER_COOKIE=secret' > .env
agent-bundle build --output artifact
agent-bundle mcp run --server my-server --target portable --artifact artifact
# server boots; process.env.MY_TRACKER_COOKIE is undefined in every handlerCurrent workaround
A wrapper script that sources the plugin .env (and exports state pins) before exec-ing agent-bundle mcp run … — i.e. the thing mcp run was meant to retire.
Suggested fix direction
Either shape works; the RFC promised the first:
- Default
loadEnvat the resolved project root.runMcp(src/api.ts) already computesworkspaceRoot = resolve(options.root), so the root to load from is in hand; rsbuild'sloadEnvconventions (.env,.env.local, mode variants) match whatcreateRslibgives the same consumers at build time. - Or an explicit, repeatable
--env-file <path>flag, if implicit file loading is considered too magical for a foreground runner that inherits stdio.
Either way, precedence should be documented: values from .env fill gaps only — a variable already present in the real process.env wins. Worth documenting in the same pass where the manifest env sits: today it is spread last and silently beats both process.env and any .env layer (see the companion anchor-expansion issue in Related, where that ordering blocks operator overrides).
Related: #59 (companion mcp-run finding, the ${PLUGIN_ROOT} anchor expansion), #50 (RFC whose §3.1 specified this behavior) — both empirically resolved by the movie-library Phase-2 migration against da5df1d; same consumer port as the #42–#47 series.
Activity
ScriptedAlchemy commentedon Aug 31, 2026
Shipped in #61 (merge
1286f5b2).mcp runnow loads the project-root.envset by default via rsbuild'sloadEnvconventions (.env,.env.local,.env.<mode>,.env.<mode>.local, with the existing--modeflag selecting variants) — the RFC #50 §3.1 direction.--env-file <path>(repeatable, Node--env-filedialect, later files win) replaces the conventional set,--no-envopts out, and a named file that cannot be read is a hard error.Precedence is documented in one place (
docs/entry-conventions.md§agent-bundle mcp run), lowest to highest: manifest env <.envfile layer < operatorprocess.env—.envfills gaps only, and an exported variable always wins, which also fixes the manifest-spread-last ordering flagged here and in #59.For the movie-library wrapper this retires the
.envsourcing block: a bareagent-bundle mcp run --server <id> --target portable --artifact artifactnow boots with the plugin-root.envcredentials loaded.