Skip to content

🤖 feat: experimental Agent Plugins 1.0.0 support (skills + MCP) - #3815

Merged
ThomasK33 merged 19 commits into
mainfrom
agent-plugins-fs0n
Aug 7, 2026
Merged

🤖 feat: experimental Agent Plugins 1.0.0 support (skills + MCP)#3815
ThomasK33 merged 19 commits into
mainfrom
agent-plugins-fs0n

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Adds experimental, feature-flagged support for the Agent Plugins 1.0.0 specification (agentplugins/agent-plugins-spec): plugins are directories with a plugin.json manifest plus optional skills/ (Agent Skills) and mcp.json (MCP servers). With the new Agent Plugins experiment enabled (Settings → Experiments, off by default), Mux discovers plugins from .mux/plugins / .agents/plugins (project) and ~/.mux/plugins / ~/.agents/plugins (global), surfaces their skills as read-only lowest-precedence skill roots, and exposes their MCP servers as default-disabled, read-only server entries that users enable per workspace.

Background

Agent Plugins is the new multi-vendor (Amazon/Cursor/Microsoft/OpenAI/Vercel TSC) portable packaging format for agent capabilities. Mux already natively implements both component types the spec defines — Agent Skills and MCP servers — so this integration reuses those pipelines rather than building a parallel plugin system. The claude-skills-compat experiment served as the direct precedent for flag-gated, read-only extra discovery roots.

Everything is gated behind EXPERIMENT_IDS.AGENT_PLUGINS (enabledByDefault: false); with the flag off, zero behavior changes.

Implementation

Discovery & manifest (src/node/services/agentPlugins/)

  • manifest.ts: validates plugin.json against the canonical 1.0.0 schema. $schema dispatches spec version (distinct unsupported-version vs invalid-manifest reasons); unknown top-level fields and non-object extensions are report-and-ignore per §5.3.
  • discovery.ts: scans container dirs for immediate children holding plugin.json (silently skips non-plugin entries like Codex marketplace.json). Realpath containment (§4.1) anchors all component paths at the canonical plugin root; §11.3 failure isolation means one broken plugin/component never affects siblings.

Skills (Phase 2): plugin skills/ dirs join AgentSkillsRoots at the lowest precedence within each scope, read-only (write/delete tools ignore plugin roots). Threaded through tools, oRPC router, stream context, and slash-command resolution.

MCP (Phase 3)

  • mcpConfig.ts implements §7.2 loading rules exactly: closed top-level ($schema + mcpServers), closed per-entry variants, per-entry skip with diagnostics, whole-file failures disable only that plugin's MCP.
  • Server identity: plugin:<sha256(realpath(root))[:16]>:<serverName> — stable across manifest renames/updates so workspace overrides and PLUGIN_DATA survive.
  • The provider is injected into MCPConfigService.listServers at lowest merge precedence (user config wins on key collisions). That single seam feeds the engine, Settings UI, WorkspaceMCPModal, and mcp.test consistently, and plugin servers structurally cannot persist: all mutations operate on the on-disk config, so plugin:* keys fail with "not found".
  • Runtime contract (§9): stdio launches get PLUGIN_ROOT/PLUGIN_DATA injected after configured env (unshadowable), single-pass ${...} expansion in args/env values/cwd only, ./-relative commands and all cwd forms containment-checked, PLUGIN_DATA (~/.mux/plugin-data/<instanceId>) created before launch. Plugin servers launch in argv mode: command + args individually shell-quoted.
  • Posture: default-disabled (per-workspace enable via existing enabledServers overrides), project-scope plugins trust-gated, remote (SSH/devcontainer) runtimes skip plugin servers, remote entries with configured headers are skipped (Mux follows redirects; §7.2.1 forbids cross-origin header forwarding).
  • The stdio config signature now includes args/env/cwd, so plugin mcp.json edits recycle servers. Existing stdio servers restart once after upgrade (signature change only).

UI: plugin entries render read-only in Settings → MCP (disabled global toggle, plugin badge, no edit/remove) and show pluginName/serverName provenance in the Workspace MCP dialog.

Validation

  • 63 new unit tests across manifest/discovery/expansion/mcpConfig plus MCPConfigService/MCPServerManager integration; full suite: 10,407 pass / 0 fail (the only local failures were the pre-existing Storybook snapshot-budget guardrails, unchanged by this branch).
  • Dogfooded end-to-end twice in dev-server sandboxes (screenshots in workspace transcript):
    • Gate A (skills): fixture plugin's /hello-greeter skill appears in slash suggestions only with the flag on.
    • Gate B (MCP): plugin servers listed read-only + default-disabled (desktop and 375 px); enabling one wrote the instance key to mcp.local.jsonc; live echo tool call through the plugin's @modelcontextprotocol/server-everything returned correctly; verified via /proc/<pid>/environ that PLUGIN_ROOT/PLUGIN_DATA were injected, placeholders expanded, cwd = plugin root, and the data dir pre-created; trust gating confirmed via API before/after trusting the project; broken fixture plugin and broken server entry isolated with diagnostics; flag-off returned zero plugin servers.

Risks

  • MCP config path is shared: MCPConfigService.listServers and the stdio launch path now have new code. Flag-off risk is minimal (provider returns {} and legacy stdio entries keep raw shell-string behavior since they never set args), but the signature change causes a one-time stdio server restart after upgrade.
  • Spec maturity: Agent Plugins 1.0.0 is freshly published; normative details could still shift. Scope here is deliberately v1-minimal (no installation/marketplace, no hooks/commands/agents, no com.coder.mux extension namespace).
  • Plugin content is untrusted input: mitigated by default-disabled MCP, trust gating for repo plugins, containment checks, argv-quoted launches, and header-forwarding refusal.

📋 Implementation Plan

Agent Plugins (1.0.0) experiment in Mux

Implement flag-gated support for the open Agent Plugins 1.0.0 format (spec repo: agentplugins/agent-plugins-spec): a plugin is a directory with a required plugin.json manifest plus optional skills/ (Agent Skills) and mcp.json (MCP servers). Mux already implements both component types natively, so this is a discovery + mapping problem, not a capability problem.

Recommended approach net LoC estimate (product code only): ~750–950 (breakdown per phase below; the MCP launch/provenance work is the expensive part). A skills-only fallback scope would be ~250 LoC (Phase 1+2 only) — not recommended since MCP mapping is where most of the portability value is, but phases are ordered so we can stop there if needed.

Spec contract we must satisfy (verified against normative spec + canonical schemas)

  • Manifest (plugin.json, §5): closed schema — only $schema, name, version, description, author, homepage, repository, license, keywords, extensions. Required: $schema (const https://agent-plugins.org/schemas/1.0.0/plugin.schema.json) and name (1–64 chars, a-z0-9.-, alphanumeric start/end, no --/..). Unknown top-level fields and non-object extensions are non-fatal: report + ignore. Any other schema violation is fatal → reject the plugin, discover/execute nothing. Unsupported $schema version → reject + report distinctly ("unsupported version").
  • Component discovery (§6, §7.1): skills = immediate child dirs of skills/ containing a regular file SKILL.md (no recursion); MCP config = mcp.json at plugin root. Missing locations are not errors; a location of the wrong filesystem kind invalidates only that component type.
  • mcp.json (§7.2): closed top-level { $schema, mcpServers }; $schema version must match plugin.json's. Server variants (closed, type required):
    • stdio: command (single executable token — bare name or ./-relative; no placeholder expansion in command), optional args: string[], env: Record<string,string> (keys must not be PLUGIN_ROOT/PLUGIN_DATA), cwd (must be ./…, ${PLUGIN_ROOT}[/…], or ${PLUGIN_DATA}[/…]; default = plugin root).
    • streamable-http / sse: url (absolute http(s); HTTPS unless loopback; no userinfo/fragment), optional literal headers (no expansion, no duplicate case-insensitive names).
  • Failure isolation (§7.2.2, §11.3): invalid mcp.json top-level → disable MCP for that plugin only; invalid/unsupported-transport server entry → skip that entry only; connect/start failure → continue. One broken plugin never affects other plugins; one broken component never affects sibling components.
  • Runtime contract (§9): stdio subprocesses get PLUGIN_ROOT (abs plugin root) + PLUGIN_DATA (client-managed writable persistent dir, created before launch, survives updates) injected after configured env overlay. ${PLUGIN_ROOT}/${PLUGIN_DATA} expansion is single-pass, non-recursive, applies to args elements, env values, and cwd only; unrecognized ${...} stays literal; no other expansion.
  • Containment (§4.1): all package paths (skills, mcp.json, ./-relative command, resolved cwd) must realpath-resolve inside the plugin root (${PLUGIN_DATA} cwd inside the data dir); escapes invalidate the narrowest scope (skill / component / server entry).

Current Mux architecture (verified in-repo)

Concern Where Relevant facts
Skill roots src/node/services/agentSkills/skillStorageContext.ts (buildProjectLocalRoots, buildGlobalLocalRoots, resolveSkillStorageContext) Sync path construction; claude-skills-compat experiment already adds flag-gated read-only roots at lowest per-scope precedence via includeClaudeSkills option
Skill scanning src/node/services/agentSkills/agentSkillsService.ts (AgentSkillsRoots, getProjectSkillRoots, getGlobalSkillRoots, discoverAgentSkills, readAgentSkill) Async, scans disk fresh per call, first-root-wins dedupe by skill name, containment helpers exist
Flag call sites agent_skill_list.ts, agent_skill_read.ts, agent_skill_read_file.ts, router.ts, streamContextBuilder.ts All check EXPERIMENT_IDS.CLAUDE_SKILLS_COMPAT / config.experiments?.claudeSkillsCompat and pass a boolean into root construction
MCP config src/node/services/mcpConfigService.ts <muxHome>/mcp.jsonc + <project>/.mux/mcp.jsonc (servers wrapper, JSONC); repo config only merged when trusted=true (Project Trust precedent); fail-closed normalization to MCPServerInfo
MCP types src/common/types/mcp.ts (+ zod in src/common/orpc/schemas/mcp.ts) MCPStdioServerInfo = { transport:"stdio", command: string, disabled, toolAllowlist? }no args/env/cwd today; http variant has url + headers (secret refs supported)
MCP lifecycle src/node/services/mcpServerManager.ts getAllServers = {...configServers, ...inlineAsInfo}; getToolsForWorkspace({workspaceId, projectPath, runtime, workspacePath, trusted, overrides, projectSecrets}); configSignature caching; stdio launch = runtime.exec(info.command, { cwd: workspacePath, … }) (shell string; ExecOptions already supports env); tool names namespaced+sanitized by buildMcpToolName
Per-workspace gating src/node/services/workspaceMcpOverridesService.ts + WorkspaceMCPModal.tsx enabledServers/disabledServers in <workspace>/.mux/mcp.local.jsonc; explicit workspace enable overrides project-level disabled: true
Experiments src/common/constants/experiments.ts (EXPERIMENT_IDS, EXPERIMENTS), src/node/services/experimentsService.ts, src/browser/features/Settings/Sections/ExperimentsSection.tsx Sync isExperimentEnabled(); new entries auto-render in Settings → Experiments

Design decisions

  1. Discovery roots (immediate child dirs containing plugin.json):
    • Project scope: <projectRoot>/.mux/plugins/, <projectRoot>/.agents/plugins/
    • Global scope: <muxHome>/plugins/ (i.e. ~/.mux/plugins), ~/.agents/plugins/
    • Assumption: ~/.agents/plugins is safe to include even though Codex drops marketplace.json files there — entries without a valid plugin.json directory are silently skipped, so there is no collision in practice.
  2. Shared plugin discovery module src/node/services/agentPlugins/ used by both the skills path and the MCP path: one manifest validator, one containment story, one diagnostics shape. No caching in v1 (matches skills scanning; MCP already has configSignature caching downstream).
  3. Skills mapping — container roots, not per-plugin threading. Extend AgentSkillsRoots with projectPluginRoots?: string[] / globalPluginRoots?: string[] (the four container dirs above); agentSkillsService enumerates <container>/<plugin>/skills/<skill>/SKILL.md for manifest-valid plugins during its existing async scan. This keeps skillStorageContext synchronous and keeps the 5 call sites to a one-boolean change (includeAgentPlugins), exactly mirroring includeClaudeSkills. Precedence: lowest within each scope (after .claude compat roots); plugins ordered alphabetically for determinism; existing first-wins dedupe handles name collisions. Plugin skills stay read-only (write/delete tools keep targeting .mux/skills only).
  4. MCP mapping — merge into getAllServers with collision-safe instance keys, default-disabled.
    • Plugin instance identity: instanceId = sha256(realpath(pluginRoot)).slice(0, 16). Server key = plugin:<instanceId>:<serverName> — collision-safe across global/project scopes, multiple projects, and duplicate plugin names in different roots, and stable across restarts, renames in the manifest, and plugin content updates (keyed on path, not name/content), so workspace enabledServers overrides stay valid. Launch metadata (rootPath, dataPath) stays backend-only on the node-side server record; anything crossing oRPC to the renderer exposes only UI-safe read-only fields { pluginName, serverName, sourceScope }. Plugin config participates in configSignature so enabling/editing a plugin recycles servers correctly.
    • Extend MCPStdioServerInfo with optional args?: string[], env?: Record<string,string>, cwd?: string (general-purpose fields; plugin normalizer populates them).
    • Launch semantics (verified in-repo): LocalBaseRuntime.exec runs bash -c <command> on every desktop platform (getBashPath(), Git Bash on Windows) and merges options.env over process.env — so POSIX quoting is correct and env overlay order is achievable. Build the command string as shellQuote(resolvedCommand) + args.map(shellQuote) reusing the existing shellQuote from src/common/utils/shell (no new quoting helper). ./-relative command resolves against the plugin root; env passed as ExecOptions.env = { ...expandedConfiguredEnv, PLUGIN_ROOT, PLUGIN_DATA } (base env → configured overlay → reserved vars last, per §9.1); cwd = expanded/validated value or plugin-root default.
    • Host-runtime scoping: plugin roots are host filesystem paths, so plugin stdio servers are only offered when the workspace runtime executes on the host (local/worktree). On SSH/Docker/devcontainer runtimes plugin MCP servers are skipped with a diagnostic (v1).
    • Security posture: plugin MCP servers normalize to disabled: true and are read-only config entries — never editable or persisted into mcp.jsonc; the only enable mechanism in v1 is the existing per-workspace enabledServers (WorkspaceMCPModal). No global-enable promise (no such override mechanism exists today). Project-scope plugins additionally require Project Trust (trusted=true), mirroring repo mcp.jsonc. Plugin skills load without a consent gate (same posture as .agents/skills today — instructions-only surface).
    • PLUGIN_DATA lives at <muxHome>/plugin-data/<instanceId> (keyed by instanceId only, so manifest renames/content updates keep the same data dir per §9.1; created before launch; deleted only manually).
    • Transport mapping: stdio→stdio, streamable-http→http, sse→sse (Mux supports all three — exceeds the spec's at-least-one minimum). Plugin headers are literal strings only (no secret-ref syntax, no expansion, per spec). Redirect/header conformance (§7.2.1): the spec forbids forwarding configured headers to a different origin via redirects, but Mux's remote transports currently use redirect: "follow"; v1 therefore fully supports headerless remote servers, and skips plugin remote servers that configure headers with a diagnostic unless same-origin redirect enforcement (or header-stripping on cross-origin redirect) is cheaply available on the AI SDK transport — decide at implementation time, default to skipping.
    • Scope resolution: the MCP plugin provider receives the same resolved scope the skills path uses (MuxToolScope via resolveMuxToolScope in aiService.ts), not raw metadata.projectPath — this handles multi-project workspaces and host-vs-runtime storage authority consistently; projectStorageAuthority === "runtime" yields no project plugin roots (matches decision 8).
  5. Version dispatch: validator recognizes exactly the 1.0.0 $schema const; anything else → "unsupported version" rejection (distinct diagnostic from "invalid manifest"). mcp.json $schema must match the manifest's version or MCP is disabled for that plugin.
  6. Diagnostics: log.warn with structured diagnostics from discovery (spec SHOULD-level reporting). No dedicated plugin UI in v1 — plugins surface through existing skill lists and MCP server lists.
  7. com.coder.mux extension namespace: reserved, not implemented. Per §8.1/§11.1: a non-object extensions value is non-fatal (report + ignore); namespace member contents are never validated — Mux implements no namespace in v1, so all extensions payloads are ignored as opaque. Do not make our validator stricter than the normative text (the JSON Schema's additionalProperties on extensions members is not enforced by us).
  8. Runtime scope: v1 discovers plugins on the host filesystem only (local runtimes; same authority rules as skillStorageContext's host-local path). SSH/devcontainer project-runtime storage is out of scope; plugin roots are simply absent there.

Implementation phases

Phase 0 — Experiment flag (~10 LoC)

  • src/common/constants/experiments.ts: add EXPERIMENT_IDS.AGENT_PLUGINS = "agent-plugins" + EXPERIMENTS entry (enabledByDefault: false, showInSettings: true, description naming the four discovery roots). Settings toggle renders automatically.
  • Verify: make typecheck; toggle visible in Settings → Experiments.

Phase 1 — Plugin discovery + manifest validation (~220 LoC)

New src/node/services/agentPlugins/:

  • manifest.ts: validatePluginManifest(raw: unknown): { ok: true; manifest; warnings: string[] } | { ok: false; reason: "unsupported-version" | "invalid-manifest"; errors: string[] }. Implements §5 exactly: permissive parse → collect+ignore unknown top-level keys and non-object extensions (warnings), strict-validate permitted fields (name pattern/length via explicit checks — the spec regex uses lookahead, fine for JS), $schema const dispatch.
  • discovery.ts: discoverAgentPlugins(containers: Array<{ path; scope }>): Promise<{ plugins: AgentPluginInfo[]; diagnostics }> — enumerate immediate child dirs, require regular-file plugin.json, validate, realpath-containment-check the plugin root; skip non-plugin entries silently (handles Codex marketplace.json), report invalid manifests. AgentPluginInfo = { name, scope, rootPath, skillsDir?, mcpConfigPath? } (component paths present only if right filesystem kind, §6.2).
  • Asserts per defensive-programming preference: container paths absolute, plugin names spec-valid post-validation.
  • Tests: manifest.test.ts + discovery.test.ts with fixture matrix — minimal valid, full manifest, unknown-field (loads + warns), bad name / missing name (fatal), wrong $schema (unsupported-version), non-object extensions (loads + warns), extension payload contents never validated, sibling broken plugin doesn't affect valid one, marketplace.json ignored.
  • Containment tests (§4.1, per-boundary): plugin.json symlink escaping root → plugin rejected; skills/ symlink escaping root → skills component invalid, MCP unaffected; individual SKILL.md symlink escape → that skill skipped; mcp.json wrong filesystem kind (dir) → MCP component invalid, skills unaffected.
  • Verify: bun test src/node/services/agentPlugins, make typecheck lint.

Phase 2 — Skills integration (~90 LoC)

  • agentSkillsService.ts: add projectPluginRoots?: string[] / globalPluginRoots?: string[] to AgentSkillsRoots; extend scan candidates so each container expands (via discoverAgentPlugins) to per-plugin skills/ roots at lowest per-scope precedence; skills failing SKILL.md validation or containment are skipped per §7.1 (existing invalid-skill diagnostics path).
  • skillStorageContext.ts: includeAgentPlugins?: boolean option populating the container fields (mirror includeClaudeSkills).
  • Call sites (5): pass includeAgentPlugins: experimentsService.isExperimentEnabled(EXPERIMENT_IDS.AGENT_PLUGINS) (or config.experiments?.agentPlugins where that pattern is used).
  • Tests: mirror the claude-skills-compat tests in agentSkillsService.test.ts, skillStorageContext.test.ts, agent_skill_list.test.ts (flag off → invisible; flag on → discovered with correct scope + precedence; plugin skill loses name collision to .mux/skills; invalid sibling skill skipped).
  • Dogfood gate A (skills): see Dogfooding section — must pass before Phase 3.

Phase 3 — MCP integration (~400 LoC)

  • src/common/types/mcp.ts + src/common/orpc/schemas/mcp.ts: add optional args/env/cwd to stdio info; shared/renderer-visible types gain only the UI-safe plugin?: { pluginName, serverName, sourceScope } provenance block, while rootPath/dataPath launch metadata lives in a backend-only record type in agentPlugins/ (nullish in zod per tool-schema conventions where applicable).
  • agentPlugins/mcpConfig.ts: loadPluginMcpServers(plugin: AgentPluginInfo, ctx: { muxHome: string }): { servers: Record<string, PluginMCPServerRecord>; diagnostics } — the ctx carries what's needed to compute dataPath; §7.2 validation (closed top-level, $schema version match, per-entry closed variants, reserved env keys, cwd forms, command token rules, URL/header rules), producing default-disabled backend server records (MCPServerInfo + backend-only launch metadata) with plugin:<instanceId>:<serverName> keys; invalid entries skipped, invalid top-level disables MCP for that plugin only; remote entries with configured headers skipped per the redirect-conformance decision.
  • agentPlugins/expansion.ts (~20 LoC): single-pass ${PLUGIN_ROOT}/${PLUGIN_DATA} replacement (non-recursive; replacement text never rescanned; unknown ${...} left literal).
  • mcpServerManager.ts: inject plugin source (constructor gains optional agentPluginsProvider); getAllServers merges plugin servers (project containers gated on trusted; provider receives MuxToolScope); plugin servers offered only on host-executing runtimes. Stdio launch for plugin servers: resolve ./ command against plugin root (realpath containment), create PLUGIN_DATA dir, expand placeholders in args/env values/cwd, validate expanded cwd containment, build command string via existing shellQuote (src/common/utils/shell) for command + each arg, exec with ExecOptions.env = { ...expandedConfiguredEnv, PLUGIN_ROOT, PLUGIN_DATA } (overlay semantics verified in LocalBaseRuntime.exec) and cwd (default plugin root). Non-plugin servers keep today's behavior exactly (no env/cwd change).
  • aiService.ts: thread the experiment gate + resolved MuxToolScope (plugin provider active only when AGENT_PLUGINS enabled).
  • UI verification subtask: confirm Settings MCP section / WorkspaceMCPModal actually list disabled: true servers from listServers so plugin servers are enableable; provenance label (pluginName/serverName) is readable; plugin entries are not editable and never persisted into mcp.jsonc; narrow viewport (~375px) renders correctly. If disabled servers are filtered out anywhere in that path, add the minimal UI/service fix (budgeted in the estimate).
  • Tests:
    • mcpConfig.test.ts: variant matrix incl. every §7.2.2 failure boundary; reserved env keys (PLUGIN_ROOT/PLUGIN_DATA) invalidate the entry; each cwd form accepted; bad cwd forms rejected.
    • Remote-entry validation: duplicate case-insensitive header names → entry invalid; url with userinfo or fragment → invalid; non-loopback http: → invalid; loopback http: accepted; entry with configured headers skipped (or, if redirect enforcement lands, cross-origin redirect does not forward configured headers).
    • expansion.test.ts: single-pass, non-recursive (replacement containing ${PLUGIN_DATA} stays literal), unknown placeholders literal.
    • Launch containment tests: ./-relative command symlink-escaping plugin root → entry invalid; expanded cwd with .. escaping plugin root → entry invalid; ${PLUGIN_DATA}/.. escape → entry invalid.
    • Key/data stability tests: same plugin root with changed manifest version/name/content → same server key and same PLUGIN_DATA path; duplicate plugin names in different roots → distinct keys and data dirs; workspace enabledServers written for a plugin key resolves to the same server after manager re-creation (simulated restart).
    • mcpServerManager.test.ts additions (mock runtime): injected env order (configured env cannot shadow PLUGIN_ROOT/PLUGIN_DATA), default cwd = plugin root, arg quoting with spaces/quotes/$/backticks/hostile strings, default-disabled + workspace enabledServers enable flow using instance keys, untrusted project excludes project plugins, remote runtime excludes plugin servers, configSignature changes when plugin mcp.json changes.
  • Dogfood gate B (MCP): see Dogfooding section.

Phase 4 — Fixture plugin, docs, final validation (~20 LoC product + fixtures)

  • Committed fixture plugin(s) under tests/fixtures/agent-plugins/ (valid hello-plugin with one skill + one stdio server; broken siblings for isolation tests) — shared by unit tests and dogfooding.
  • docs/: short experiment page added to docs.json nav (what the experiment does, discovery roots, trust/enable model). Skip if we decide experiments stay undocumented — confirm against existing experiment docs precedent at implementation time.
  • Full validation: make static-check, make test, targeted suites above.

Dogfooding (required gates, with evidence)

Environment: dev-server-sandbox skill (isolated MUX_ROOT + ports) + agent-browser for UI interaction; screenshots via agent-browser screenshot --full and a short screen recording (ffmpeg -f x11grab fallback per environment notes) attached with attach_file.

Gate A — skills (after Phase 2):

  1. Start sandbox; enable agent-plugins in Settings → Experiments (screenshot).
  2. Install fixture plugin: mkdir -p <MUX_ROOT>/plugins && cp -r tests/fixtures/agent-plugins/hello-plugin <MUX_ROOT>/plugins/.
  3. In a workspace chat, run a prompt that calls agent_skill_list; verify the plugin skill appears with global scope (screenshot of tool result); agent_skill_read the skill and confirm body loads. Also drop a broken sibling plugin and re-list to prove isolation (skill list unaffected, warning in backend log).
  4. Toggle experiment off → plugin skill disappears (screenshot).

Gate B — MCP (after Phase 3):

  1. Fixture plugin's mcp.json declares a stdio server per the spec's token rules — { "type": "stdio", "command": "bunx", "args": ["-y", "@modelcontextprotocol/server-everything"], "env": { "DATA_DIR": "${PLUGIN_DATA}/d" } } — plus an args variant exercising ${PLUGIN_ROOT}.
  2. Verify server shows disabled by default in Settings/WorkspaceMCPModal (screenshot, including a ~375px narrow-viewport screenshot since this touches Settings/modal UI); enable it for the workspace (screenshot).
  3. Send a message; enable llmDebugLogs and inspect sessions/<ws>/devtools.jsonl rawRequest.tools to confirm namespaced plugin tools registered; invoke one tool end-to-end (screenshot of tool call in chat).
  4. Verify PLUGIN_DATA dir was created under <MUX_ROOT>/plugin-data/... and that a server with an invalid sibling entry still starts (isolation).
  5. Untrusted-project check: place a plugin in a project .mux/plugins of an untrusted project → MCP servers absent until trusted.

Out of scope (v1)

Installation/distribution/marketplaces, update checks, plugin signing, dedicated plugin-management UI, com.coder.mux extension behavior, hooks/commands/agents component types, remote-runtime (SSH/devcontainer) plugin roots, caching of plugin discovery.

Risks

  • Spec is a working draft (repo README says 1.0.0 unpublished; spec text says Published). Mitigation: experiment flag default-off; validator centralizes the version const so a rename is one-line.
  • Prompt injection via plugin skills — same exposure as existing .agents/skills; accepted for the experiment (skills load only when the user placed the plugin dir and enabled the flag).
  • Arg quoting through runtime.exec shell string — bounded: plugin servers only run on host runtimes, where LocalBaseRuntime.exec is always bash -c (incl. Git Bash on Windows), and quoting reuses the existing shellQuote helper with hostile-arg tests; long-term cleanup would be argv-array exec support in Runtime, deliberately not in scope.
  • mcpServerManager complexity creep — mitigated by keeping all plugin-specific logic in agentPlugins/ and giving the manager only a provider interface + launch-time env hook.

Acceptance criteria

  1. Flag off → zero behavior change (no new roots scanned, no plugin servers listed); full test suite green.
  2. Flag on → fixture plugin's skill discoverable/readable via skill tools at documented precedence; write tools untouched.
  3. Flag on → plugin MCP servers listed default-disabled for host-executing workspaces (remote runtimes skip them with a diagnostic); workspace-enable launches with PLUGIN_ROOT/PLUGIN_DATA injected, placeholders expanded single-pass, default cwd = plugin root; tools callable.
  4. Every §11.3/§7.2.2 failure boundary covered by a test (broken plugin/skill/mcp.json/server entry never disables valid siblings).
  5. Project-scope plugin MCP servers absent until the project is trusted; plugin skills follow the existing project-skill posture and are gated only by the agent-plugins experiment.
  6. make static-check + make test pass; both dogfood gates evidenced with screenshots/recording.

Generated with mux • Model: anthropic:claude-fable-5 • Thinking: xhigh • Cost: $239.09

Phase 0+1 of Agent Plugins (agent-plugins.org 1.0.0) support:
- New 'agent-plugins' experiment (default off, shown in Settings)
- src/node/services/agentPlugins/: plugin.json manifest validation (§5)
  with distinct unsupported-version vs invalid-manifest rejection, and
  container discovery (§4/§6) with realpath containment and per-plugin/
  per-component failure isolation (§11.3)
Phase 2 of Agent Plugins support (flag-gated by agent-plugins experiment):
- AgentSkillsRoots gains projectPluginRoots/globalPluginRoots container dirs
- Scan candidates expand plugin containers to per-plugin skills/ roots at
  lowest per-scope precedence, with §4.1 plugin-root containment plus the
  existing project containment posture for project-scope plugin roots
- resolveSkillStorageContext/getDefaultAgentSkillsRoots take
  includeAgentPlugins (host-local only; remote runtimes never scan plugins)
- Flag threaded through agent_skill_list/read/read_file, router,
  streamContextBuilder, aiService, agentSession; write tools untouched
Phase 3 of the agent-plugins experiment: plugin mcp.json files (Agent
Plugins 1.0.0 §7.2) normalize into default-disabled, read-only MCP server
records keyed plugin:<instanceId>:<serverName>, merged into
MCPConfigService.listServers at lowest precedence so both the engine and
the UI see them. Stdio launches run in quoted argv mode with PLUGIN_ROOT/
PLUGIN_DATA injected after configured env, single-pass placeholder
expansion, containment-validated command/cwd, and the plugin data dir
created before launch. Plugin servers are skipped on remote runtimes and
enabled per workspace via existing enabledServers overrides.
Follows the claude-skills-compat precedent: a skills-discovery section in
agent-skills.mdx and a server section in mcp-servers.mdx covering the
default-disabled / read-only / trust-gated / host-only posture. Also fixes
a stray prettier formatting nit in agentSkillsService.test.ts.
@mintlify

mintlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Mux 🟢 Ready View Preview Aug 6, 2026, 8:49 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ddf5f3be3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/mcpServerManager.ts Outdated
Comment thread src/node/services/agentPlugins/mcpConfig.ts Outdated
…very

1. Devcontainer exclusion: DevcontainerRuntime extends LocalBaseRuntime but
   execs inside the container, so plugin servers (host paths in command/cwd/
   PLUGIN_ROOT/PLUGIN_DATA) were wrongly offered there. AIService now resolves
   an AgentPluginsMcpContext (null for SSH/Docker/devcontainer/multi-project)
   and the manager's runtime backstop also excludes DevcontainerRuntime.

2. Worktree discovery: workspace MCP flows now scan the ACTIVE checkout
   (resolveMuxProjectRootForHostFs) so plugin content follows the branch,
   matching skill discovery. Project plugin instance IDs now hash
   projectKey + container-relative location instead of the checkout realpath,
   so the engine (worktree) and UI (project checkout) agree on
   plugin:<id>:<name> keys and PLUGIN_DATA stays stable across worktrees.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: efd4a961a4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/mcpConfigService.ts
Codex round 2: WorkspaceMCPModal listed plugin servers from the project
checkout while the engine discovered them from the active worktree, so
branch-only plugins were invisible to the modal and off-host workspaces
listed servers the engine filters out.

resolveAgentPluginsMcpContext is now metadata-based and lives in
agentPlugins/mcpConfig.ts so AIService and the oRPC router resolve the
identical context. mcp.list and mcp.test accept an optional workspaceId;
the router maps it to the workspace's context (worktree scan for host
workspaces, null for SSH/Docker/devcontainer/multi-project) and
mcpServerManager.test forwards it for named-server lookups. The modal
passes workspaceId on both list and fetch-tools calls.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31762c406f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/mcpServerManager.ts Outdated
Codex round 3: a valid ${PLUGIN_DATA}/nested cwd passed normalization but
only the PLUGIN_DATA root was created at launch, so exec() rejected the
missing cwd on first start. prepareStdioLaunch now mkdir -p's cwds
contained in the data dir (client-managed writable state); plugin-root
cwds are shipped content and stay untouched.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7731cf3f92

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/aiService.ts Outdated
Comment thread src/node/services/agentPlugins/mcpConfig.ts Outdated
… cwds

Codex round 4:

1. subProjectPath workspaces: the stream path scanned the execution path
   (<checkout>/<subproject>/.mux/plugins) while the oRPC listing path used
   resolveWorkspaceRootPath, so the modal could enable a checkout-root
   plugin the engine then omitted. The stream context now also resolves
   the checkout root via resolveWorkspaceRootPath.

2. Plugin-root-anchored cwds (./x, ${PLUGIN_ROOT}/x) now require strict
   existence: launch only creates PLUGIN_DATA dirs and exec() rejects a
   missing cwd, so accepting one produced an enableable server that could
   never start. ${PLUGIN_DATA} cwds keep the allow-missing behavior, and a
   lexical pre-check still reports ../-style breakouts as escapes.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d89b54961

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/orpc/router.ts
Comment thread src/node/services/agentSkills/skillStorageContext.ts
Comment thread src/browser/components/WorkspaceMCPModal/WorkspaceMCPModal.tsx
Comment thread src/node/services/agentPlugins/mcpConfig.ts
… skill containers, workspace-scoped test cache, directory cwd check
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dcbfc0699a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/agentSession.ts Outdated
Comment thread src/node/services/tools/agent_skill_list.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e3ef82ced1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/agentPlugins/mcpConfig.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ebb91d17a4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/agentPlugins/mcpConfig.ts
Comment thread src/node/services/tools/agent_skill_list.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a822257b0e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/agentPlugins/mcpConfig.ts
Comment thread src/node/services/agentPlugins/mcpConfig.ts
Comment thread src/node/services/agentPlugins/mcpConfig.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 043d65b85e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/orpc/router.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a77d3dcef0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/components/WorkspaceMCPModal/WorkspaceMCPModal.tsx
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 3f516da5fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ea5cb2f3a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/features/Settings/Sections/MCPSettingsSection.tsx
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6159691fc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/browser/components/WorkspaceMCPModal/WorkspaceMCPModal.tsx Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05cc3d48fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/mcpServerManager.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: f07e2d8239

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
ThomasK33 added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit c994e45 Aug 7, 2026
22 checks passed
@ThomasK33
ThomasK33 deleted the agent-plugins-fs0n branch August 7, 2026 07:47
@mux-bot mux-bot Bot mentioned this pull request Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant