Skip to content

chore: dual-host plugin (Claude + Codex) via Node.js MCP bridge#140

Merged
firstintent merged 1 commit into
mainfrom
chore/dual-host-plugin
May 24, 2026
Merged

chore: dual-host plugin (Claude + Codex) via Node.js MCP bridge#140
firstintent merged 1 commit into
mainfrom
chore/dual-host-plugin

Conversation

@firstintent
Copy link
Copy Markdown
Owner

What

Same repo now serves as both a Claude Code plugin and a Codex plugin. A zero-npm-dep Node.js bridge (index.js) handles binary lazy download + host detection + stdio proxy to ccteam mcp-serve.

Why

  • Single-command install: /plugin install ccteam (Claude) or codex plugin marketplace add firstintent/ccteam (Codex). No install.sh required, no ~/.local/bin/ PATH wrangling.
  • Binary in plugin sandbox = PATH-independent; bridge auto-symlinks to ~/.local/bin/ccteam so terminal ccteam start still works.
  • CCTEAM_HOST env injected → Rust binary can branch behavior per host (V0.6.7 candidate; this PR does not act on it).
  • install.sh kept as system-wide CLI fallback for headless / non-Claude/Codex workflows; README + quickstart lead with the plugin path.
  • Version SoT = .claude-plugin/plugin.json::version — index.js reads it at startup and re-downloads the binary whenever ccteam --version does not match; no hardcoded version strings inside the bridge.

Architecture

/plugin install ccteam  (Claude)   or   codex plugin marketplace add firstintent/ccteam
   |
   v Anthropic / Codex protocol clones repo + registers .mcp.json
   |
   v user calls any mcp__ccteam__* tool
   |
   v host spawns `node ${PLUGIN_ROOT}/index.js`
       |
       v index.js sniffs host (CLAUDE_PLUGIN_ROOT vs CODEX_ACTIVE/PLUGIN_ROOT env)
       v checks ${PLUGIN_ROOT}/bin/ccteam exists + version matches plugin.json
       v if missing/stale: download ccteam-vX.Y.Z-<platform>.tar.gz from GH Release
                          extract via `tar -xz --strip-components=1` -> ${PLUGIN_ROOT}/bin/
                          chmod +x, symlink ~/.local/bin/ccteam -> ${PLUGIN_ROOT}/bin/ccteam
       v soft-warn if tmux not on PATH (chat-mode features unavailable)
       v exec ccteam mcp-serve with env CCTEAM_HOST=claude|codex
       v pipe stdin/stdout (stderr passes through to host for diagnostics)

Files

  • New: index.js (218 LOC, zero npm deps — stdlib only), .codex-plugin/plugin.json (rich Codex UI metadata: interface block, brandColor, defaultPrompt).
  • Modified: .mcp.json (command: ccteam -> node ${CLAUDE_PLUGIN_ROOT}/index.js), .claude-plugin/plugin.json + .claude-plugin/marketplace.json (version drift 0.6.5 -> 0.6.6, matching workspace SoT — no workspace bump), README.md (lead with plugin install, install.sh demoted to "Advanced"), docs/quickstart.md (Step 1 rewritten: no CLI prereq; Codex sub-section added), docs/troubleshooting.md (3 new entries A3b/A3c/A3 update: bridge download failures, missing PATH symlink).

Verify

  • node --check index.js -> clean
  • JSON validity (all 4 manifests: .claude-plugin/plugin.json, .claude-plugin/marketplace.json, .codex-plugin/plugin.json, .mcp.json) -> clean
  • cargo clippy --workspace --all-targets --locked -- -D warnings -> 0 warnings (no Rust touched)
  • cargo fmt --all -- --check -> clean
  • sh -n install.sh && dash -n install.sh -> clean
  • Baseline cargo test --workspace --locked --no-fail-fast -> 1545 passed, 95 failed; all 95 failures pre-exist on origin/main (ccteam-web SSE / SPA / webhook integration tests, env-dependent — port binding / 502 on this WSL host); zero new failures introduced (no Rust source modified).

Test plan

  • Tag a v0.6.6 release once merged so the GH Release tarballs exist (current firstintent/ccteam has no release yet; bridge needs assets to download).
  • Verify on Linux x86_64: /plugin install ccteam in a fresh Claude session, call any mcp__ccteam__* tool, confirm ~/.local/bin/ccteam appears.
  • Verify on macOS arm64: same as Linux + Gatekeeper bypass.
  • Verify in a Codex session: codex plugin marketplace add firstintent/ccteam, confirm bridge picks CCTEAM_HOST=codex.
  • Manual fallback: if ${CLAUDE_PLUGIN_ROOT} env is not substituted by the host, switch .mcp.json to absolute path and re-test.

Architecture:
- index.js (zero-npm-dep Node.js bridge) becomes the .mcp.json command.
  On first MCP invocation in either host, index.js detects host
  (CLAUDE_PLUGIN_ROOT vs CODEX_ACTIVE/PLUGIN_ROOT env), checks for cached
  binary at ${PLUGIN_ROOT}/bin/ccteam, downloads + extracts the
  per-platform tarball from the matching GitHub Release if missing or
  stale (version pinned via plugin.json — SoT), chmods, symlinks to
  ~/.local/bin/ccteam for CLI access, then execs `ccteam mcp-serve` with
  CCTEAM_HOST=claude|codex env injected.
- .codex-plugin/plugin.json added with rich Codex UI metadata (interface
  block, brandColor, defaultPrompt).
- .claude-plugin/plugin.json kept slim; version drift 0.6.5 -> 0.6.6
  realigned to workspace SoT (no workspace bump).
- .mcp.json `command` switches from `ccteam` (PATH-dependent) to
  `node ${CLAUDE_PLUGIN_ROOT}/index.js` (sandbox-resident, no
  install.sh prerequisite).
- install.sh kept as alt path for system-wide CLI; README + quickstart
  lead with plugin install, install.sh demoted to "Advanced" section.

User-visible: single command install (no install.sh required) on both
Claude Code and Codex. Binary lives in plugin sandbox, PATH-independent.
tmux warning soft (not fatal). Version auto-aligned via plugin.json SoT,
no hardcoded version strings inside index.js.

No workspace version bump (workspace stays 0.6.6); only plugin manifest
drift correction 0.6.5 -> 0.6.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@firstintent firstintent merged commit 633bcad into main May 24, 2026
1 of 2 checks passed
firstintent added a commit that referenced this pull request May 24, 2026
User tested PR #140 on real macOS and found `/plugin install ccteam`
cannot install the Rust binary via the Node bridge (likely
${CLAUDE_PLUGIN_ROOT} substitution / sandbox / permission issue —
not investigated further per user direction).

Reverting to the proven two-step install:
1. curl install.sh | sh — downloads + installs ccteam binary to ~/.local/bin
2. /plugin marketplace add + /plugin install ccteam — installs skills + .mcp.json

Files reverted (to d7b4201 state):
- .mcp.json (command "ccteam" on PATH, not node + index.js)
- README.md (install.sh-then-plugin lead restored)
- docs/quickstart.md (Step 1 binary install + Step 2 plugin install)
- docs/troubleshooting.md (removed bridge-specific entries)

Files removed:
- index.js (Node bridge — dead path)
- .codex-plugin/ (dual-host manifest — unused now)

Files kept from PR #140 (good drift fix, unrelated to revert):
- .claude-plugin/{plugin,marketplace}.json version 0.6.5 → 0.6.6 sync

No workspace version bump. No code/test impact.

Future Codex-side integration (if desired): separate finding, will not
use Node bridge approach.

Co-authored-by: cs <dev@cs>
firstintent added a commit that referenced this pull request May 24, 2026
…ME (#142)

Codex-side manifest is useful in the two-step install model:
- Step 1: install.sh → binary on $PATH
- Step 2a (Claude): /plugin install reads .claude-plugin/plugin.json
- Step 2b (Codex):  codex plugin marketplace add reads .codex-plugin/plugin.json
- Both wire .mcp.json (command "ccteam") → spawn ccteam mcp-serve on PATH

Restored from PR #140 (which was reverted in PR #141):
- .codex-plugin/plugin.json (rich interface metadata for Codex UI)

Added to README install section:
- Codex install one-liner (`codex plugin marketplace add firstintent/ccteam`)

No code change, no version bump, no test impact.

Co-authored-by: cs <dev@cs>
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