Summary
Installing a built plugin/cursor artifact into ~/.cursor/plugins/local/<name> per the generated AGENTS.md instructions produces a plugin Cursor never loads. Verified against a current Cursor build (Linux, Aug 2026) by comparing with two locally working plugins.
Evidence from working local plugins
- Manifest location: both working local plugins use a root
plugin.json (one has no manifest at all and loads purely by convention). Nothing reads .cursor-plugin/plugin.json, which is where the adapter puts the Cursor manifest (with hooks and mcpServers pointers that are therefore dead).
- Hooks document collision (unified
plugin target): under convention loading, Cursor would discover hooks/hooks.json — but in the unified bundle that file is the Claude/Codex document (PascalCase SessionStart/PreToolUse, ${CLAUDE_PLUGIN_ROOT} commands). The actual Cursor document is at hooks/hooks-cursor.json, referenced only from the unread .cursor-plugin/plugin.json. Net effect: silently zero Cursor hooks (or worse, a Claude-format doc parsed and ignored).
- Event schema itself is correct: Cursor's current first-party hook documentation confirms
preToolUse + matcher: "^Shell$" + permission/updated_input/additional_context — the codec output is right; only packaging/discovery is wrong.
${CURSOR_PLUGIN_ROOT} expansion is unproven for local plugins: if Cursor doesn't export it, the emitted node "${CURSOR_PLUGIN_ROOT}/hooks/x.cursor.mjs" commands can't resolve, and AGENT_BUNDLE_PLUGIN_ROOT-anchored state resolution falls back to PATH lookups. Working local plugins use absolute or plugin-root-relative commands (user hooks run from ~/.cursor/).
- Working plugins also declare MCP via a root
mcp.json, not .cursor-plugin/mcp.json.
Workaround used
A hand-built install directory in the tracedecay-plugin shape: root plugin.json ("hooks": "hooks/hooks.json", "skills": "skills/"), a Cursor-format hooks/hooks.json whose commands are absolute-path sh shims exporting AGENT_BUNDLE_PLUGIN_ROOT/CURSOR_PLUGIN_ROOT before exec'ing the generated *.cursor.mjs wrappers, root mcp.json with absolute args + env anchor, and a copied skills/. The generated wrappers themselves work perfectly once reachable.
Suggested fixes
- Standalone
cursor target: emit root plugin.json + root mcp.json (keep .cursor-plugin/ only if some Cursor surface actually consumes it).
- Unified
plugin target: don't let the Claude-format hooks/hooks.json shadow the Cursor doc — either a per-host hooks dir or a root Cursor manifest pointing at hooks/hooks-cursor.json explicitly.
- Re-verify
${CURSOR_PLUGIN_ROOT} against a real local-plugin install (the capability provenance cites docs + installed marketplace plugins; local-dir plugins appear to behave differently), or emit env-pinning wrapper commands.
Observed with agent-bundle preview 560124af; consumer repo: cargo-conductor.
Summary
Installing a built
plugin/cursorartifact into~/.cursor/plugins/local/<name>per the generated AGENTS.md instructions produces a plugin Cursor never loads. Verified against a current Cursor build (Linux, Aug 2026) by comparing with two locally working plugins.Evidence from working local plugins
plugin.json(one has no manifest at all and loads purely by convention). Nothing reads.cursor-plugin/plugin.json, which is where the adapter puts the Cursor manifest (withhooksandmcpServerspointers that are therefore dead).plugintarget): under convention loading, Cursor would discoverhooks/hooks.json— but in the unified bundle that file is the Claude/Codex document (PascalCaseSessionStart/PreToolUse,${CLAUDE_PLUGIN_ROOT}commands). The actual Cursor document is athooks/hooks-cursor.json, referenced only from the unread.cursor-plugin/plugin.json. Net effect: silently zero Cursor hooks (or worse, a Claude-format doc parsed and ignored).preToolUse+matcher: "^Shell$"+permission/updated_input/additional_context— the codec output is right; only packaging/discovery is wrong.${CURSOR_PLUGIN_ROOT}expansion is unproven for local plugins: if Cursor doesn't export it, the emittednode "${CURSOR_PLUGIN_ROOT}/hooks/x.cursor.mjs"commands can't resolve, andAGENT_BUNDLE_PLUGIN_ROOT-anchored state resolution falls back to PATH lookups. Working local plugins use absolute or plugin-root-relative commands (user hooks run from~/.cursor/).mcp.json, not.cursor-plugin/mcp.json.Workaround used
A hand-built install directory in the tracedecay-plugin shape: root
plugin.json("hooks": "hooks/hooks.json","skills": "skills/"), a Cursor-formathooks/hooks.jsonwhose commands are absolute-pathshshims exportingAGENT_BUNDLE_PLUGIN_ROOT/CURSOR_PLUGIN_ROOTbefore exec'ing the generated*.cursor.mjswrappers, rootmcp.jsonwith absolute args + env anchor, and a copiedskills/. The generated wrappers themselves work perfectly once reachable.Suggested fixes
cursortarget: emit rootplugin.json+ rootmcp.json(keep.cursor-plugin/only if some Cursor surface actually consumes it).plugintarget: don't let the Claude-formathooks/hooks.jsonshadow the Cursor doc — either a per-host hooks dir or a root Cursor manifest pointing athooks/hooks-cursor.jsonexplicitly.${CURSOR_PLUGIN_ROOT}against a real local-plugin install (the capability provenance cites docs + installed marketplace plugins; local-dir plugins appear to behave differently), or emit env-pinning wrapper commands.Observed with agent-bundle preview
560124af; consumer repo: cargo-conductor.