Skip to content

fix(plugin): support BASE MCP in Codex - #4

Open
mh0pe wants to merge 16 commits into
ChristopherKahler:mainfrom
mh0pe:codex/codex-plugin-mcp-registration
Open

fix(plugin): support BASE MCP in Codex#4
mh0pe wants to merge 16 commits into
ChristopherKahler:mainfrom
mh0pe:codex/codex-plugin-mcp-registration

Conversation

@mh0pe

@mh0pe mh0pe commented Jul 23, 2026

Copy link
Copy Markdown

What changed

  • add a native .codex-plugin/plugin.json for BASE
  • register the MCP server with a plugin-relative launcher and Codex-resolved cwd
  • preserve the caller's project directory before importing the BASE MCP server
  • add an end-to-end initialize/tools-list regression that asserts all 21 tools
  • add Codex install and registration checks to the plugin CI workflow

Root cause

The legacy .mcp.json passes ${CLAUDE_PLUGIN_ROOT}/mcp/index.js as an MCP
argument. Claude Code expands that placeholder, but Codex treats MCP arguments
literally, so Node receives a nonexistent path.

The Codex-native manifest uses the supported relative-cwd contract and an
explicit launcher. Claude Code continues to use the existing .mcp.json.

Validation

  • node --test tests/codex-plugin.test.mjs
  • actionlint .github/workflows/plugin-install.yml
  • isolated CODEX_HOME marketplace add, plugin install, and codex mcp list
  • MCP initialize handshake plus tools/list returning 21 tools

Review note

This branch is based on the integrated mh0pe/base fork main, so the upstream
comparison also contains the native-plugin work already proposed in #1, #2,
and #3. The Codex-specific change is commit 84beab6.

mh0pe and others added 15 commits June 13, 2026 22:49
Prefer CLAUDE_PROJECT_DIR when set and non-blank; fall back to the
existing __dirname/HOOK_DIR relative resolution unchanged. Never throws
on missing env var. Affected: src/packages/base-mcp/index.js and all
six auto-register hooks (active, backlog, base-pulse-check, operator,
satellite-detection, psmm-injector) plus apex-insights.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lugin

Adds --skills-dir [--dir <path>] flag to bin/install.js (purely additive,
no existing flags changed). Installs a Claude Code skills-directory plugin
at <cwd>/.claude/skills/base/ by default (or --dir <path>). Creates
.claude-plugin/plugin.json, commands/, skills/, base-framework/ (with
hooks/ and packages/), hooks/ + hooks.json (UserPromptSubmit + SessionStart
wiring), and mcp/ + .mcp.json. All @~/.claude/base-framework/ and
@./.claude/base-framework/ refs in copied .md files are rewritten to
${CLAUDE_PLUGIN_ROOT}/base-framework/. Hook and MCP command paths use
${CLAUDE_PLUGIN_ROOT}. Prints load instructions on completion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add .claude-plugin/plugin.json (name=base, version=3.1.5 from package.json)
- Add .claude-plugin/marketplace.json for claude plugin marketplace add
- Copy src/commands -> commands/ with namespace-strip (base: prefix removed)
- Copy src/skill/base.md -> skills/base/base.md
- Copy src/framework -> base-framework/ (tasks, templates, context, etc.)
- Copy src/hooks -> hooks/ with CLAUDE_PROJECT_DIR-first WORKSPACE_ROOT fix
- Copy src/packages/base-mcp -> mcp/ with CLAUDE_PROJECT_DIR-first WORKSPACE_PATH fix
- Wire hooks/hooks.json: 5 UserPromptSubmit + 1 SessionStart (apex-insights excluded)
- Add .mcp.json registering base-mcp via node ${CLAUDE_PLUGIN_ROOT}/mcp/index.js
- Rewrite @~/.claude/base-framework/ -> @${CLAUDE_PLUGIN_ROOT}/base-framework/ in
  commands/, skills/, base-framework/ plugin-native tree (66 refs)
- src/ tree untouched: npx install reads src/ which retains @~/.claude/ refs
- bin/install.js: add copyFileExpandingMacro() to expand ${CLAUDE_PLUGIN_ROOT}
  during any copy, ensuring no literal placeholder in npx-installed output

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…E_PROJECT_DIR hooks, conflict guard)

- Finding 1 (HIGH): move .mcp.json write target from mcp/.mcp.json to
  path.join(targetBase, '.mcp.json') so it lands at the plugin root.
  The arg path \${CLAUDE_PLUGIN_ROOT}/mcp/base-mcp/index.js is unchanged.
- Finding 2 (HIGH): merge origin/feat/workspace-project-dir into this branch
  so all 7 emitted hooks carry CLAUDE_PROJECT_DIR-first workspace resolution
  (falls back to HOOK_DIR.parent.parent when env is unset). base-mcp/index.js
  receives the same layered resolution. Plugin requires CLAUDE_PROJECT_DIR to
  be set at runtime; Claude Code sets it automatically for plugin hooks.
  This branch depends on feat/workspace-project-dir — note for PR description.
- Finding 3 (MEDIUM): add conflict guard — --skills-dir combined with --global
  or --local prints an error and exits non-zero before installSkillsDir() runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Emit hooks/install-mcp-deps.py (SessionStart hook) that installs
@modelcontextprotocol/sdk deps into CLAUDE_PLUGIN_DATA and symlinks
PLUGIN_ROOT/mcp/base-mcp/node_modules -> PLUGIN_DATA/node_modules so
Node ESM can resolve bare specifiers from the importing file's directory.

NODE_PATH added to .mcp.json env (CJS fallback; Node ESM requires the
symlink bridge since ESM does not honour NODE_PATH at import time).

- Idempotent: skips install if sdk marker already present; re-asserts symlink
- Fail-open: warns to stderr and exits 0 on any error (npm missing, env unset)
- Keeps all 5 UserPromptSubmit hooks + satellite-detection SessionStart hook
- Adds install-mcp-deps.py as second SessionStart hook entry
- Script emitted inline (not in src/hooks/) so it stays scoped to skills-dir

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the duplicate src/ framework/command/hook/mcp trees that
shadowed the root plugin-native tree (commands/, base-framework/,
skills/, hooks/, mcp/). The root tree uses ${CLAUDE_PLUGIN_ROOT}
macros and is the single committed source of truth.

bin/install.js now reads from the root tree for all install modes
(--global, --local, --workspace). copyFileExpandingMacro is now
exercised for every copy path, substituting ${CLAUDE_PLUGIN_ROOT}
with the resolved install target so no literal placeholder survives
in npx-installed output.

Retained: src/templates/ (workspace.json + operator.json) — these
are the npx workspace-mode templates; they contain no macros and
have no equivalent in the root tree.

package.json files[] updated to ship the root tree instead of src/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add committed hooks/install-mcp-deps.py (SessionStart hook) that
installs @modelcontextprotocol/sdk deps into CLAUDE_PLUGIN_DATA and
symlinks PLUGIN_ROOT/mcp/node_modules -> PLUGIN_DATA/node_modules so
Node ESM can resolve bare specifiers from the importing file's directory.

The MCP server lives at mcp/index.js (not mcp/base-mcp/index.js as in
the install-skills-dir branch). The symlink is placed at mcp/node_modules
adjacent to mcp/index.js so ESM's directory-walk resolution succeeds.

NODE_PATH added to .mcp.json env (CJS fallback; Node ESM requires the
symlink bridge since ESM does not honour NODE_PATH at import time).

- Idempotent: skips install if sdk marker already present; re-asserts symlink
- Fail-open: warns to stderr and exits 0 on any error (npm missing, env unset)
- Keeps 5 UserPromptSubmit hooks + satellite-detection SessionStart hook
- Adds install-mcp-deps as second SessionStart hook entry
- npx-mode safety: hook is harmless/fail-open when CLAUDE_PLUGIN_DATA unset

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… symlink

- .gitignore: add slash-less `mcp/node_modules` entry so the SessionStart
  symlink (created by install-mcp-deps.py) is properly ignored.
  The existing `node_modules/` pattern matches directories only; a trailing
  slash does NOT match symlinks, causing the link to appear as untracked.
- .github/workflows/plugin-install.yml: validate + auth-free install smoke
  on every push/PR (identical shape to paul/seed/carl native workflows).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ve source tree, dual-mode npx, MCP-deps bootstrap, CI, .gitignore symlink fix

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…converged root tree

Semantic conflict resolution: installSkillsDir() re-pointed from deleted src/
subtrees to the converged root layout introduced by feat/native-plugin:
  src/commands       -> commands/
  src/skill          -> skills/base/
  src/framework      -> base-framework/
  src/hooks          -> hooks/
  src/packages/base-mcp -> mcp/ (flat layout, index.js at mcp/index.js)

MCP bootstrap deduped: replaced embedded heredoc install-mcp-deps.py with a
file-copy from root hooks/install-mcp-deps.py (single source of truth).
MCP .mcp.json args updated to mcp/index.js (not mcp/base-mcp/index.js).
Symlink target corrected to mcp/node_modules (flat layout).

Hook conflicts (active-hook.py, backlog-hook.py, base-pulse-check.py,
operator.py, psmm-injector.py, satellite-detection.py, apex-insights.py)
and mcp/index.js resolved to HEAD (native-plugin's more complete
CLAUDE_PROJECT_DIR resolution with .base/ guard and home-dir protection).

feat/workspace-project-dir is transitively subsumed via e780c10 in
feat/install-skills-dir's history — all CLAUDE_PROJECT_DIR workspace
resolution is present in mcp/index.js and hooks. Not re-merged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: adopt native-plugin + skills-dir + workspace/MCP capabilities
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mh0pe
mh0pe marked this pull request as ready for review July 23, 2026 16:17
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