Skip to content

refactor(plugin/codex): switch MCP from local stdio to OV /mcp directly#2022

Merged
ZaynJarvis merged 2 commits into
volcengine:mainfrom
t0saki:feat/codex-mcp-remote-http
May 13, 2026
Merged

refactor(plugin/codex): switch MCP from local stdio to OV /mcp directly#2022
ZaynJarvis merged 2 commits into
volcengine:mainfrom
t0saki:feat/codex-mcp-remote-http

Conversation

@t0saki
Copy link
Copy Markdown
Collaborator

@t0saki t0saki commented May 13, 2026

Summary

Follow-up to #2019 (which fixed Codex 0.130 startup) — now that all four hooks and the MCP launcher work, the local stdio MCP server is dead weight. Codex 0.130 supports streamable-HTTP MCP servers with bearer auth via bearer_token_env_var in .mcp.json, and OpenViking server has exposed /mcp natively since 1.27. So we can delete our bundled MCP middleman entirely and have Codex talk to OV directly.

What changes

  • .mcp.json switched to url + bearer_token_env_var: "OPENVIKING_API_KEY" + env_http_headers for the multi-tenant identity headers. URL is a __OPENVIKING_MCP_URL__ placeholder; installer renders it from ovcli.conf.url / OPENVIKING_URL at install time. API key never lands on disk in the cached .mcp.json — it's pulled from process env at codex launch.

  • setup-helper/install.sh:

    • Resolves the OV /mcp URL (priority: OPENVIKING_MCP_URL > OPENVIKING_URL/mcp > ovcli.conf.url/mcp > localhost) and sed-renders it into the cached .mcp.json.
    • Appends a marker-delimited codex() shell function to ~/.zshrc / ~/.bashrc that promotes ovcli.conf fields into env vars (OPENVIKING_API_KEY, _ACCOUNT, _USER, _AGENT_ID) before exec'ing codex. Mirrors the claude-code installer's claude() wrapper. Idempotent — replaces the existing block in place rather than appending.
  • Deleted: src/memory-server.ts, servers/memory-server.js, tsconfig.json, package.json, package-lock.json, scripts/bootstrap-runtime.mjs, scripts/runtime-common.mjs, scripts/start-memory-server.mjs. Net ~2400 lines removed. Hook scripts remain zero-dep .mjs running on Codex's bundled Node 22.

  • README + docs/{en,zh}/agent-integrations/04-codex.md rewritten:

    • Drop "npm install" / "build" / "manual stdio" sections.
    • MCP tools list and protocol details now linked to guides/06-mcp-integration.md instead of duplicated.
    • Added a codex() shell-function wrapper snippet for manual setup.
  • Plugin version: 0.4.1 → 0.5.0.

What the user gets

Aspect Before (#1957 + #2019) After (this PR)
MCP server Local stdio: node bootstraps runtime, npm ci of @modelcontextprotocol/sdk + zod, ~600 LOC TS proxy Streamable HTTP → OV /mcp directly
Tools exposed 4 (recall, store, forget, health) 9 (add_resource, forget, glob, grep, health, list, read, search, store)
First-launch latency npm-ci + cold node spawn before MCP handshake (10s+) Single HTTP initialize call
API key on disk In ovcli.conf only In ovcli.conf only (shell wrapper promotes to env at launch; .mcp.json references via bearer_token_env_var, never embeds)
Build deps tsc + npm + lockfile None
Lines of code to maintain ~1.2k ~0 (deleted the proxy)

Validation

End-to-end on Codex 0.130 against a live OV server:

$ codex
$ /mcp
🔌 MCP Tools
  • openviking-memory
    • Auth: Bearer token
    • Tools: add_resource, forget, glob, grep, health, list, read, search, store

$ <prompt>
• SessionStart hook (completed)
  warning: SessionStart(startup): committed 1 OpenViking session(s) (heuristic=0, idle=1)
• Called openviking-memory.health({})
  └ OpenViking is healthy (service initialized, storage: VikingFS)
• Stop hook (completed)
  warning: appended 2 turn(s) to OpenViking session <id>

Tradeoffs / notes

  • URL change still needs a re-install (URL is baked into the cached .mcp.json). API key rotation only needs a codex restart (env re-read via the wrapper).
  • No-/mcp-endpoint users (older OV servers, deeply locked-down deployments) lose the MCP tool path. The hook path (auto-recall / auto-capture) still works since it goes via REST, but the model can't call search / store / etc. as MCP tools. Users on OV ≥ 1.27 are unaffected.
  • The local stdio server was only ever a thin proxy of OV REST → MCP. The decision to delete vs. keep mirrors the conversation in earlier memory threads ("商业版加 /mcp 之后基本冗余").
  • env_http_headers silently omits missing env vars — users who don't set OPENVIKING_USER just don't send X-OpenViking-User; OV server applies its defaults.

Test plan

  • .mcp.json placeholder __OPENVIKING_MCP_URL__ is rendered by installer
  • Cached .mcp.json URL matches ovcli.conf.url + /mcp (or OPENVIKING_URL env override, with priority documented)
  • bearer_token_env_var: "OPENVIKING_API_KEY" produces Auth: Bearer token in /mcp, not the OAuth fallback Codex falls back to when no auth is configured
  • All 9 OV MCP tools listed in /mcp and health round-trip works
  • All four lifecycle hooks complete cleanly (no longer needs runtime-common.mjs import.meta.url fallback because the launcher is gone)
  • Shell function wrapper block is marker-delimited and replaced in place on re-install (not duplicated)
  • Rotating api_key in ovcli.conf then restarting codex picks up the new key without re-install
  • No ${CODEX_PLUGIN_ROOT} / ${CODEX_PLUGIN_DATA} / package.json / node_modules / servers/ / src/ in checked-in plugin
  • DESIGN.md / VERIFICATION.md / 04-codex.md (zh+en) all aligned with the new architecture

… (http)

Codex 0.130 supports streamable-HTTP MCP servers with bearer auth via
`bearer_token_env_var` in `.mcp.json` (and per-header env binding via
`env_http_headers`). OpenViking server has exposed `/mcp` natively since
1.27, so the local stdio MCP middleman (`src/memory-server.ts` +
`servers/memory-server.js` + the npm-ci runtime bootstrap) is dead weight:
the model now gets a strictly larger tool set (search, store, read, list,
grep, glob, forget, add_resource, health — vs the previous recall/store/
forget/health) by talking to OV directly, and the plugin loses its only
build/dependency surface.

What changed

- `.mcp.json`: switched to `url` + `bearer_token_env_var: "OPENVIKING_API_KEY"`
  + `env_http_headers` for the multi-tenant identity headers. URL is a
  `__OPENVIKING_MCP_URL__` placeholder; installer renders it from ovcli.conf
  / `OPENVIKING_URL` at install time. API key never lands on disk in the
  cached .mcp.json — it's pulled from process env at codex launch.

- `setup-helper/install.sh`: resolves the OV /mcp URL (OPENVIKING_MCP_URL >
  OPENVIKING_URL/mcp > ovcli.conf.url/mcp > localhost), renders the
  .mcp.json placeholder into the cached copy, and appends a `codex()` shell
  function wrapper to the user's rc that promotes ovcli.conf fields into
  env vars before exec'ing codex (mirrors the claude-code-memory-plugin
  pattern; needed because Codex reads OPENVIKING_API_KEY from process env
  at MCP launch, not from any file).

- Deleted: `src/memory-server.ts`, `servers/memory-server.js`, `tsconfig.json`,
  `package.json`, `package-lock.json`, `scripts/bootstrap-runtime.mjs`,
  `scripts/runtime-common.mjs`, `scripts/start-memory-server.mjs`. Net
  ~2400 lines removed. Hook scripts remain zero-dep .mjs running on
  Codex's bundled Node 22.

- README + docs/{en,zh}/agent-integrations/04-codex.md: rewritten to
  describe the new architecture. The MCP tools list and protocol details
  are now referenced via a link to docs/{en,zh}/guides/06-mcp-integration.md
  rather than duplicated in the plugin docs.

- Plugin version: 0.4.1 → 0.5.0.

Validation

Verified end-to-end on Codex 0.130 against `ov-dev.tosaki.top`:

  /mcp
  🔌 MCP Tools
    • openviking-memory
      • Auth: Bearer token
      • Tools: add_resource, forget, glob, grep, health, list, read, search, store

`openviking-memory.health` returned `OpenViking is healthy ... storage: VikingFS`;
Stop hook reported `appended 2 turn(s) to OpenViking session <id>`.

Notes

- `.mcp.json` headers that don't have a corresponding env var (e.g. user
  didn't set `OPENVIKING_USER`) are simply not sent — `env_http_headers`
  silently omits missing vars per Codex's MCP runtime.
- Rotating the API key now just needs `codex` restart (env re-reads from
  ovcli.conf via the wrapper). URL changes still need a re-install since
  the URL is baked into the cached .mcp.json.
- The shell function wrapper has a marker-delimited block so re-running
  the installer replaces it in place rather than appending duplicates.
@github-actions
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🏅 Score: 85
🧪 No relevant tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Missing jq dependency check

The codex() shell function wrapper uses jq to read ovcli.conf fields, but the installer does not check if jq is installed. This can lead to silent failures where the MCP server lacks auth credentials (OPENVIKING_API_KEY, etc.) because the wrapper can't extract them from the config file.

need() {
  command -v "$1" >/dev/null 2>&1 || {
    echo "Missing required command: $1" >&2
    exit 1
  }

@github-actions
Copy link
Copy Markdown

PR Code Suggestions ✨

No code suggestions found for the PR.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the Codex memory plugin to remove the bundled local stdio MCP proxy and instead connect Codex directly to OpenViking’s native /mcp endpoint over streamable HTTP with Bearer auth. It also updates the installer and docs to support URL placeholder rendering and env-based auth/identity propagation at launch time.

Changes:

  • Switch examples/codex-memory-plugin/.mcp.json from command/stdio to url + bearer_token_env_var + env_http_headers, with install-time rendering of __OPENVIKING_MCP_URL__.
  • Update setup-helper/install.sh to resolve the /mcp endpoint, render cached config placeholders, and install an rc codex() wrapper that injects env vars from ovcli.conf.
  • Delete the local MCP server implementation and all npm/ts build artifacts; rewrite README and integration docs; bump plugin version to 0.5.0.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
examples/codex-memory-plugin/VERIFICATION.md Updates verification SOP header version to v0.5.0.
examples/codex-memory-plugin/.mcp.json Converts MCP configuration to streamable HTTP URL + env-based Bearer auth + identity headers.
examples/codex-memory-plugin/.codex-plugin/plugin.json Bumps plugin version to 0.5.0.
examples/codex-memory-plugin/setup-helper/install.sh Adds /mcp URL resolution + placeholder rendering and installs an rc codex() wrapper to inject env vars at launch.
examples/codex-memory-plugin/README.md Updates architecture and setup docs for direct /mcp usage; removes local MCP server build/runtime guidance.
docs/en/agent-integrations/04-codex.md Updates integration doc for direct /mcp usage and new installer behavior.
docs/zh/agent-integrations/04-codex.md Updates integration doc (zh) for direct /mcp usage and new installer behavior.
examples/codex-memory-plugin/tsconfig.json Deletes TS build config (no longer needed).
examples/codex-memory-plugin/src/memory-server.ts Deletes the TS-based local stdio MCP server implementation.
examples/codex-memory-plugin/servers/memory-server.js Deletes the compiled local MCP server output.
examples/codex-memory-plugin/scripts/start-memory-server.mjs Deletes MCP launcher (no local MCP server).
examples/codex-memory-plugin/scripts/runtime-common.mjs Deletes npm runtime bootstrap logic (no local MCP server).
examples/codex-memory-plugin/scripts/bootstrap-runtime.mjs Deletes runtime bootstrap hook helper (no local MCP server).
examples/codex-memory-plugin/package.json Deletes npm package manifest (no local deps).
examples/codex-memory-plugin/package-lock.json Deletes npm lockfile (no local deps).
Files not reviewed (1)
  • examples/codex-memory-plugin/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +248 to +256
if grep -qF "$WRAPPER_MARKER_BEGIN" "$RC"; then
echo "Replacing existing openviking codex() wrapper in $RC"
awk -v b="$WRAPPER_MARKER_BEGIN" -v e="$WRAPPER_MARKER_END" '
$0 == b {skip=1; next}
$0 == e {skip=0; next}
!skip
' "$RC" > "$RC.tmp" && mv "$RC.tmp" "$RC"
else
echo "Appending codex() wrapper to $RC"
Comment on lines +295 to +297
Next:
source $RC # pick up the codex() wrapper
codex # restart codex; review /hooks if prompted
Comment on lines +262 to +266
local _ov_conf="\${OPENVIKING_CLI_CONFIG_FILE:-\$HOME/.openviking/ovcli.conf}"
if [ -f "\$_ov_conf" ] && command -v jq >/dev/null 2>&1; then
OPENVIKING_URL="\${OPENVIKING_URL:-\$(jq -r '.url // empty' "\$_ov_conf" 2>/dev/null)}" \\
OPENVIKING_API_KEY="\${OPENVIKING_API_KEY:-\$(jq -r '.api_key // empty' "\$_ov_conf" 2>/dev/null)}" \\
OPENVIKING_ACCOUNT="\${OPENVIKING_ACCOUNT:-\$(jq -r '.account // empty' "\$_ov_conf" 2>/dev/null)}" \\
Comment thread docs/en/agent-integrations/04-codex.md Outdated
The installer checks `codex`, `git`, and Node.js 22+, refreshes (or clones on first run) `~/.openviking/openviking-repo`, registers a local `openviking-plugins-local` marketplace, enables `openviking-memory@openviking-plugins-local`, sets `features.plugin_hooks = true`, and pre-populates Codex's plugin cache so the plugin resolves immediately. Rerunning the installer is idempotent — it always pulls latest before installing.

It uses `~/.openviking/ovcli.conf` when present; otherwise the plugin falls back to `http://127.0.0.1:1933` unless `OPENVIKING_URL` / `OPENVIKING_API_KEY` are set in the env.
It reads `~/.openviking/ovcli.conf` for the OpenViking URL, renders the `/mcp` endpoint into the cached `.mcp.json`, and appends a `codex()` shell function to your rc that pulls `OPENVIKING_API_KEY` (and `_ACCOUNT` / `_USER`) from ovcli.conf at every codex invocation. The API key stays in `ovcli.conf`; the `.mcp.json` on disk only references `OPENVIKING_API_KEY` via `bearer_token_env_var`, never embeds it.
Comment thread docs/zh/agent-integrations/04-codex.md Outdated
脚本会检查 `codex`、`git`、Node.js 22+;首次运行时把 OpenViking 仓库 clone 到 `~/.openviking/openviking-repo`,已存在则自动 `git fetch + reset --hard` 到 main;注册本地 `openviking-plugins-local` marketplace、启用 `openviking-memory@openviking-plugins-local`、把 `features.plugin_hooks = true` 写入 `~/.codex/config.toml`,并预填 Codex 的 plugin 缓存让插件立即解析到。每一步幂等,反复执行安全。

存在 `~/.openviking/ovcli.conf` 时直接读它;否则插件回落到 `http://127.0.0.1:1933`,或读取 env 中的 `OPENVIKING_URL` / `OPENVIKING_API_KEY`
存在 `~/.openviking/ovcli.conf` 时直接读它,把 `/mcp` URL 渲染进缓存里的 `.mcp.json`;同时往你的 shell rc 追加一个 `codex()` 函数包装,每次调用 codex 时从 ovcli.conf 把 `OPENVIKING_API_KEY` / `_ACCOUNT` / `_USER` 注入到环境变量。API key 只留在 `ovcli.conf` 里,**`.mcp.json` 磁盘文件里只通过 `bearer_token_env_var` 引用变量名,永远不会包含 key 明文**
1. Switch the codex() shell-function wrapper from jq to node. The installer
   already hard-requires node 22+, while jq is not always present; the old
   wrapper would silently fall through to `command codex` with no env
   injection when jq was missing, which caused Codex to start with no
   Bearer token, OV to return 401, and Codex to drop into its OAuth
   fallback. Now there is a single tool dependency for both the installer
   and the wrapper it emits.

2. Marker-replacement is now defensive: rewrite-in-place only triggers
   when BOTH the BEGIN and END markers exist in the rc. If only BEGIN
   is present (manual edit / corruption), warn and append a fresh block
   instead of awk-dropping everything from BEGIN to EOF.

3. When no rc is detected, omit the `source $RC` line from the final
   "Next:" hint and tell the user to paste the snippet manually instead
   of printing `source ` with a trailing space.

4. Docs (README + 04-codex.md zh/en): use the full env var names
   (OPENVIKING_API_KEY / OPENVIKING_ACCOUNT / OPENVIKING_USER /
   OPENVIKING_AGENT_ID) instead of `_ACCOUNT` / `_USER` shorthand;
   update the manual-setup snippets to the node-based wrapper.

The wrapper body is now defined once and reused for both the appended-to-rc
path and the manual-paste path, so the two cannot drift.
@ZaynJarvis ZaynJarvis merged commit b007611 into volcengine:main May 13, 2026
7 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in OpenViking project May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants