🤖 feat: experimental Agent Plugins 1.0.0 support (skills + MCP) - #3815
Conversation
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.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
@codex review |
There was a problem hiding this comment.
💡 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".
…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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
… 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.
|
@codex review |
There was a problem hiding this comment.
💡 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".
… skill containers, workspace-scoped test cache, directory cwd check
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
There was a problem hiding this comment.
💡 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".
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Summary
Adds experimental, feature-flagged support for the Agent Plugins 1.0.0 specification (agentplugins/agent-plugins-spec): plugins are directories with a
plugin.jsonmanifest plus optionalskills/(Agent Skills) andmcp.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-compatexperiment 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: validatesplugin.jsonagainst the canonical 1.0.0 schema.$schemadispatches spec version (distinctunsupported-versionvsinvalid-manifestreasons); unknown top-level fields and non-objectextensionsare report-and-ignore per §5.3.discovery.ts: scans container dirs for immediate children holdingplugin.json(silently skips non-plugin entries like Codexmarketplace.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 joinAgentSkillsRootsat 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.tsimplements §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.plugin:<sha256(realpath(root))[:16]>:<serverName>— stable across manifest renames/updates so workspace overrides andPLUGIN_DATAsurvive.MCPConfigService.listServersat lowest merge precedence (user config wins on key collisions). That single seam feeds the engine, Settings UI, WorkspaceMCPModal, andmcp.testconsistently, and plugin servers structurally cannot persist: all mutations operate on the on-disk config, soplugin:*keys fail with "not found".PLUGIN_ROOT/PLUGIN_DATAinjected 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.enabledServersoverrides), project-scope plugins trust-gated, remote (SSH/devcontainer) runtimes skip plugin servers, remote entries with configuredheadersare skipped (Mux follows redirects; §7.2.1 forbids cross-origin header forwarding).args/env/cwd, so pluginmcp.jsonedits recycle servers. Existing stdio servers restart once after upgrade (signature change only).UI: plugin entries render read-only in Settings → MCP (disabled global toggle,
pluginbadge, no edit/remove) and showpluginName/serverNameprovenance in the Workspace MCP dialog.Validation
/hello-greeterskill appears in slash suggestions only with the flag on.mcp.local.jsonc; liveechotool call through the plugin's@modelcontextprotocol/server-everythingreturned correctly; verified via/proc/<pid>/environthatPLUGIN_ROOT/PLUGIN_DATAwere 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
MCPConfigService.listServersand 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 setargs), but the signature change causes a one-time stdio server restart after upgrade.com.coder.muxextension namespace).📋 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 requiredplugin.jsonmanifest plus optionalskills/(Agent Skills) andmcp.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)
plugin.json, §5): closed schema — only$schema,name,version,description,author,homepage,repository,license,keywords,extensions. Required:$schema(consthttps://agent-plugins.org/schemas/1.0.0/plugin.schema.json) andname(1–64 chars,a-z0-9.-, alphanumeric start/end, no--/..). Unknown top-level fields and non-objectextensionsare non-fatal: report + ignore. Any other schema violation is fatal → reject the plugin, discover/execute nothing. Unsupported$schemaversion → reject + report distinctly ("unsupported version").skills/containing a regular fileSKILL.md(no recursion); MCP config =mcp.jsonat 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 };$schemaversion must matchplugin.json's. Server variants (closed,typerequired):stdio:command(single executable token — bare name or./-relative; no placeholder expansion incommand), optionalargs: string[],env: Record<string,string>(keys must not bePLUGIN_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 literalheaders(no expansion, no duplicate case-insensitive names).mcp.jsontop-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.PLUGIN_ROOT(abs plugin root) +PLUGIN_DATA(client-managed writable persistent dir, created before launch, survives updates) injected after configuredenvoverlay.${PLUGIN_ROOT}/${PLUGIN_DATA}expansion is single-pass, non-recursive, applies toargselements,envvalues, andcwdonly; unrecognized${...}stays literal; no other expansion.mcp.json,./-relativecommand, resolvedcwd) 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)
src/node/services/agentSkills/skillStorageContext.ts(buildProjectLocalRoots,buildGlobalLocalRoots,resolveSkillStorageContext)claude-skills-compatexperiment already adds flag-gated read-only roots at lowest per-scope precedence viaincludeClaudeSkillsoptionsrc/node/services/agentSkills/agentSkillsService.ts(AgentSkillsRoots,getProjectSkillRoots,getGlobalSkillRoots,discoverAgentSkills,readAgentSkill)agent_skill_list.ts,agent_skill_read.ts,agent_skill_read_file.ts,router.ts,streamContextBuilder.tsEXPERIMENT_IDS.CLAUDE_SKILLS_COMPAT/config.experiments?.claudeSkillsCompatand pass a boolean into root constructionsrc/node/services/mcpConfigService.ts<muxHome>/mcp.jsonc+<project>/.mux/mcp.jsonc(serverswrapper, JSONC); repo config only merged whentrusted=true(Project Trust precedent); fail-closed normalization toMCPServerInfosrc/common/types/mcp.ts(+ zod insrc/common/orpc/schemas/mcp.ts)MCPStdioServerInfo={ transport:"stdio", command: string, disabled, toolAllowlist? }— noargs/env/cwdtoday; http variant hasurl+headers(secret refs supported)src/node/services/mcpServerManager.tsgetAllServers={...configServers, ...inlineAsInfo};getToolsForWorkspace({workspaceId, projectPath, runtime, workspacePath, trusted, overrides, projectSecrets}); configSignature caching; stdio launch =runtime.exec(info.command, { cwd: workspacePath, … })(shell string;ExecOptionsalready supportsenv); tool names namespaced+sanitized bybuildMcpToolNamesrc/node/services/workspaceMcpOverridesService.ts+WorkspaceMCPModal.tsxenabledServers/disabledServersin<workspace>/.mux/mcp.local.jsonc; explicit workspace enable overrides project-leveldisabled: truesrc/common/constants/experiments.ts(EXPERIMENT_IDS,EXPERIMENTS),src/node/services/experimentsService.ts,src/browser/features/Settings/Sections/ExperimentsSection.tsxisExperimentEnabled(); new entries auto-render in Settings → ExperimentsDesign decisions
plugin.json):<projectRoot>/.mux/plugins/,<projectRoot>/.agents/plugins/<muxHome>/plugins/(i.e.~/.mux/plugins),~/.agents/plugins/~/.agents/pluginsis safe to include even though Codex dropsmarketplace.jsonfiles there — entries without a validplugin.jsondirectory are silently skipped, so there is no collision in practice.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).AgentSkillsRootswithprojectPluginRoots?: string[]/globalPluginRoots?: string[](the four container dirs above);agentSkillsServiceenumerates<container>/<plugin>/skills/<skill>/SKILL.mdfor manifest-valid plugins during its existing async scan. This keepsskillStorageContextsynchronous and keeps the 5 call sites to a one-boolean change (includeAgentPlugins), exactly mirroringincludeClaudeSkills. Precedence: lowest within each scope (after.claudecompat roots); plugins ordered alphabetically for determinism; existing first-wins dedupe handles name collisions. Plugin skills stay read-only (write/delete tools keep targeting.mux/skillsonly).getAllServerswith collision-safe instance keys, default-disabled.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 workspaceenabledServersoverrides 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 inconfigSignatureso enabling/editing a plugin recycles servers correctly.MCPStdioServerInfowith optionalargs?: string[],env?: Record<string,string>,cwd?: string(general-purpose fields; plugin normalizer populates them).LocalBaseRuntime.execrunsbash -c <command>on every desktop platform (getBashPath(), Git Bash on Windows) and mergesoptions.envoverprocess.env— so POSIX quoting is correct and env overlay order is achievable. Build the command string asshellQuote(resolvedCommand) + args.map(shellQuote)reusing the existingshellQuotefromsrc/common/utils/shell(no new quoting helper)../-relativecommandresolves against the plugin root; env passed asExecOptions.env = { ...expandedConfiguredEnv, PLUGIN_ROOT, PLUGIN_DATA }(base env → configured overlay → reserved vars last, per §9.1);cwd= expanded/validated value or plugin-root default.disabled: trueand are read-only config entries — never editable or persisted intomcp.jsonc; the only enable mechanism in v1 is the existing per-workspaceenabledServers(WorkspaceMCPModal). No global-enable promise (no such override mechanism exists today). Project-scope plugins additionally require Project Trust (trusted=true), mirroring repomcp.jsonc. Plugin skills load without a consent gate (same posture as.agents/skillstoday — instructions-only surface).PLUGIN_DATAlives 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).stdio→stdio,streamable-http→http,sse→sse(Mux supports all three — exceeds the spec's at-least-one minimum). Pluginheadersare 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 useredirect: "follow"; v1 therefore fully supports headerless remote servers, and skips plugin remote servers that configureheaderswith 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.MuxToolScopeviaresolveMuxToolScopeinaiService.ts), not rawmetadata.projectPath— this handles multi-project workspaces and host-vs-runtime storage authority consistently;projectStorageAuthority === "runtime"yields no project plugin roots (matches decision 8).$schemaconst; anything else → "unsupported version" rejection (distinct diagnostic from "invalid manifest").mcp.json$schemamust match the manifest's version or MCP is disabled for that plugin.log.warnwith structured diagnostics from discovery (spec SHOULD-level reporting). No dedicated plugin UI in v1 — plugins surface through existing skill lists and MCP server lists.com.coder.muxextension namespace: reserved, not implemented. Per §8.1/§11.1: a non-objectextensionsvalue is non-fatal (report + ignore); namespace member contents are never validated — Mux implements no namespace in v1, so allextensionspayloads are ignored as opaque. Do not make our validator stricter than the normative text (the JSON Schema'sadditionalPropertiesonextensionsmembers is not enforced by us).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: addEXPERIMENT_IDS.AGENT_PLUGINS = "agent-plugins"+EXPERIMENTSentry (enabledByDefault: false,showInSettings: true, description naming the four discovery roots). Settings toggle renders automatically.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-objectextensions(warnings), strict-validate permitted fields (name pattern/length via explicit checks — the spec regex uses lookahead, fine for JS),$schemaconst dispatch.discovery.ts:discoverAgentPlugins(containers: Array<{ path; scope }>): Promise<{ plugins: AgentPluginInfo[]; diagnostics }>— enumerate immediate child dirs, require regular-fileplugin.json, validate, realpath-containment-check the plugin root; skip non-plugin entries silently (handles Codexmarketplace.json), report invalid manifests.AgentPluginInfo = { name, scope, rootPath, skillsDir?, mcpConfigPath? }(component paths present only if right filesystem kind, §6.2).manifest.test.ts+discovery.test.tswith 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.plugin.jsonsymlink escaping root → plugin rejected;skills/symlink escaping root → skills component invalid, MCP unaffected; individualSKILL.mdsymlink escape → that skill skipped;mcp.jsonwrong filesystem kind (dir) → MCP component invalid, skills unaffected.bun test src/node/services/agentPlugins,make typecheck lint.Phase 2 — Skills integration (~90 LoC)
agentSkillsService.ts: addprojectPluginRoots?: string[]/globalPluginRoots?: string[]toAgentSkillsRoots; extend scan candidates so each container expands (viadiscoverAgentPlugins) to per-pluginskills/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?: booleanoption populating the container fields (mirrorincludeClaudeSkills).includeAgentPlugins: experimentsService.isExperimentEnabled(EXPERIMENT_IDS.AGENT_PLUGINS)(orconfig.experiments?.agentPluginswhere that pattern is used).claude-skills-compattests inagentSkillsService.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).Phase 3 — MCP integration (~400 LoC)
src/common/types/mcp.ts+src/common/orpc/schemas/mcp.ts: add optionalargs/env/cwdto stdio info; shared/renderer-visible types gain only the UI-safeplugin?: { pluginName, serverName, sourceScope }provenance block, whilerootPath/dataPathlaunch metadata lives in a backend-only record type inagentPlugins/(nullish in zod per tool-schema conventions where applicable).agentPlugins/mcpConfig.ts:loadPluginMcpServers(plugin: AgentPluginInfo, ctx: { muxHome: string }): { servers: Record<string, PluginMCPServerRecord>; diagnostics }— thectxcarries what's needed to computedataPath; §7.2 validation (closed top-level,$schemaversion 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) withplugin:<instanceId>:<serverName>keys; invalid entries skipped, invalid top-level disables MCP for that plugin only; remote entries with configuredheadersskipped 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 optionalagentPluginsProvider);getAllServersmerges plugin servers (project containers gated ontrusted; provider receivesMuxToolScope); plugin servers offered only on host-executing runtimes. Stdio launch for plugin servers: resolve./command against plugin root (realpath containment), createPLUGIN_DATAdir, expand placeholders inargs/envvalues/cwd, validate expandedcwdcontainment, build command string via existingshellQuote(src/common/utils/shell) for command + each arg, exec withExecOptions.env = { ...expandedConfiguredEnv, PLUGIN_ROOT, PLUGIN_DATA }(overlay semantics verified inLocalBaseRuntime.exec) andcwd(default plugin root). Non-plugin servers keep today's behavior exactly (no env/cwd change).aiService.ts: thread the experiment gate + resolvedMuxToolScope(plugin provider active only whenAGENT_PLUGINSenabled).disabled: trueservers fromlistServersso plugin servers are enableable; provenance label (pluginName/serverName) is readable; plugin entries are not editable and never persisted intomcp.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).mcpConfig.test.ts: variant matrix incl. every §7.2.2 failure boundary; reservedenvkeys (PLUGIN_ROOT/PLUGIN_DATA) invalidate the entry; eachcwdform accepted; badcwdforms rejected.urlwith userinfo or fragment → invalid; non-loopbackhttp:→ invalid; loopbackhttp:accepted; entry with configuredheadersskipped (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../-relativecommandsymlink-escaping plugin root → entry invalid; expandedcwdwith..escaping plugin root → entry invalid;${PLUGIN_DATA}/..escape → entry invalid.version/name/content → same server key and samePLUGIN_DATApath; duplicate plugin names in different roots → distinct keys and data dirs; workspaceenabledServerswritten for a plugin key resolves to the same server after manager re-creation (simulated restart).mcpServerManager.test.tsadditions (mock runtime): injected env order (configured env cannot shadowPLUGIN_ROOT/PLUGIN_DATA), default cwd = plugin root, arg quoting with spaces/quotes/$/backticks/hostile strings, default-disabled + workspaceenabledServersenable flow using instance keys, untrusted project excludes project plugins, remote runtime excludes plugin servers, configSignature changes when plugin mcp.json changes.Phase 4 — Fixture plugin, docs, final validation (~20 LoC product + fixtures)
tests/fixtures/agent-plugins/(validhello-pluginwith one skill + one stdio server; broken siblings for isolation tests) — shared by unit tests and dogfooding.docs/: short experiment page added todocs.jsonnav (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.make static-check,make test, targeted suites above.Dogfooding (required gates, with evidence)
Environment:
dev-server-sandboxskill (isolatedMUX_ROOT+ ports) +agent-browserfor UI interaction; screenshots viaagent-browser screenshot --fulland a short screen recording (ffmpeg -f x11grabfallback per environment notes) attached withattach_file.Gate A — skills (after Phase 2):
agent-pluginsin Settings → Experiments (screenshot).mkdir -p <MUX_ROOT>/plugins && cp -r tests/fixtures/agent-plugins/hello-plugin <MUX_ROOT>/plugins/.agent_skill_list; verify the plugin skill appears with global scope (screenshot of tool result);agent_skill_readthe skill and confirm body loads. Also drop a broken sibling plugin and re-list to prove isolation (skill list unaffected, warning in backend log).Gate B — MCP (after Phase 3):
mcp.jsondeclares 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 anargsvariant exercising${PLUGIN_ROOT}.llmDebugLogsand inspectsessions/<ws>/devtools.jsonlrawRequest.toolsto confirm namespaced plugin tools registered; invoke one tool end-to-end (screenshot of tool call in chat).PLUGIN_DATAdir was created under<MUX_ROOT>/plugin-data/...and that a server with an invalid sibling entry still starts (isolation)..mux/pluginsof 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.muxextension behavior, hooks/commands/agents component types, remote-runtime (SSH/devcontainer) plugin roots, caching of plugin discovery.Risks
.agents/skills; accepted for the experiment (skills load only when the user placed the plugin dir and enabled the flag).runtime.execshell string — bounded: plugin servers only run on host runtimes, whereLocalBaseRuntime.execis alwaysbash -c(incl. Git Bash on Windows), and quoting reuses the existingshellQuotehelper with hostile-arg tests; long-term cleanup would be argv-array exec support inRuntime, deliberately not in scope.mcpServerManagercomplexity creep — mitigated by keeping all plugin-specific logic inagentPlugins/and giving the manager only a provider interface + launch-time env hook.Acceptance criteria
PLUGIN_ROOT/PLUGIN_DATAinjected, placeholders expanded single-pass, default cwd = plugin root; tools callable.agent-pluginsexperiment.make static-check+make testpass; both dogfood gates evidenced with screenshots/recording.Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh• Cost:$239.09