Skip to content

mcp run expands ${PLUGIN_ROOT} and the AGENT_BUNDLE_PLUGIN_ROOT anchor to the artifact target root, fragmenting durable state per rebuild #59

Description

@ScriptedAlchemy

Package: agent-bundle (mcp run), at main da5df1d (PR #52, RFC #50 Phase 1). Found empirically by the movie-library Phase-2 migration onto that commit.

What happens

Under mcp run, the manifest's ${PLUGIN_ROOT} token — and with it the AGENT_BUNDLE_PLUGIN_ROOT env anchor that adapters inject on every stdio server since PR #49 (#47's fix) — expands to the artifact target root, not the project root:

  • resolveMcpStdioLaunch (src/services/mcp-run.ts) resolves tokens with pluginRoot: targetRoot, where targetRoot = joinArtifact(artifact, target) — e.g. artifact/portable.
  • Every emitted stdio server carries env.AGENT_BUNDLE_PLUGIN_ROOT = "${PLUGIN_ROOT}" (withPluginRootEnvAnchor, src/adapters/types.ts), so under mcp run the documented state anchor points into the build output directory.
  • runMcpForeground spawns with env: { ...process.env, ...launch.env } — expanded manifest env last — so an operator's export AGENT_BUNDLE_PLUGIN_ROOT=… before mcp run is silently overridden. (A config-declared anchor wins over the injected one by design, per the comment on withPluginRootEnvAnchor, but the operator-shell layer has no such escape.)

Contrast with the same function one layer up: runMcp (src/api.ts) already computes workspaceRoot = resolve(options.root) and anchors ${PLUGIN_DATA} durably at <project>/.agent-bundle/mcp-run/<target>/<server>. The framework already chose project-root durability for plugin-data; the plugin-root anchor is the inconsistent one.

Empirical consequence

Movie-library's state resolution trusts the anchor exactly as #47's fix intends. A bare mcp run handshake against the checkout's artifact/ wrote persistent state to artifact/portable/.runtime — fragmenting per rebuild and dying with artifact regeneration. That is precisely the state-fragmentation defect (per-tracker policy ledger, live Trakt device auth) the anchor was introduced to prevent in host installs. The consumer's wrapper therefore stays, pinning MOVIE_LIBRARY_ROOT / MOVIE_LIBRARY_STATE_DIR (layer 1 of its state-root chain, which beats the anchor).

The semantics question

Both expansions are defensible, which is why this is filed as a question and not just a defect:

  • Under a host install, ${PLUGIN_ROOT} = install root is correct — the artifact is the installation, and it is durable.
  • Under mcp run against a checkout's artifact/, the artifact is an ephemeral build product; the "plugin root" in the operator's mental model is the checkout.

One nuance constrains the fix: ${PLUGIN_ROOT} in args/cwd genuinely must remain the artifact target root — args[0] is the content-hashed bundle inside it. It is specifically the env-anchor semantics that mislead runtimes into anchoring durable state on a directory with build-product lifetime.

Suggested fix directions

  1. Under mcp run, expand env-value occurrences of ${PLUGIN_ROOT} (or at minimum the injected AGENT_BUNDLE_PLUGIN_ROOT) to the resolved project root, keeping args/cwd on the artifact target root. The machinery exists: the token resolver already supports per-field token→root maps (McpPathTokenMap, src/services/mcp-path-tokens.ts), and runMcp already holds workspaceRoot. Tradeoff: dev runs gain checkout-state continuity (matching wrapper behavior and host-install durability), at the cost of mcp run no longer being a byte-faithful rehearsal of a copied-artifact launch — a --plugin-root <path> override restores that when wanted.
  2. Keep artifact-root expansion as a documented default, add --plugin-root <path>, and let the operator's environment win: today export AGENT_BUNDLE_PLUGIN_ROOT=… is silently beaten by the manifest anchor because of the env spread order. Weaker: every consumer learns the flag only after state has fragmented once.
  3. Documentation-only: state that under mcp run the anchor points into artifact/<target> and durable state should ride ${PLUGIN_DATA} (already durably anchored at <project>/.agent-bundle/mcp-run/…). Weakest: it contradicts the claude adapter drops cwd for source-built stdio servers: no working-directory anchor on bare installs (portable and codex both provide one) #47-era guidance to trust the anchor, and plugins that share state between host installs and dev runs (movie-library does) cannot split roots per launch mode.

Related: #58 (companion mcp-run finding, missing .env loading), #50 (RFC #50 Phase 1 shipped mcp run), #47 (the anchor this expansion undermines was introduced there and fixed by PR #49) — empirically resolved by the movie-library Phase-2 migration against da5df1d.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions