refactor(plugin/codex): switch MCP from local stdio to OV /mcp directly#2022
Conversation
… (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.
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨No code suggestions found for the PR. |
There was a problem hiding this comment.
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.jsonfromcommand/stdiotourl+bearer_token_env_var+env_http_headers, with install-time rendering of__OPENVIKING_MCP_URL__. - Update
setup-helper/install.shto resolve the/mcpendpoint, render cached config placeholders, and install an rccodex()wrapper that injects env vars fromovcli.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.
| 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" |
| Next: | ||
| source $RC # pick up the codex() wrapper | ||
| codex # restart codex; review /hooks if prompted |
| 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)}" \\ |
| 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. |
| 脚本会检查 `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.
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_varin.mcp.json, and OpenViking server has exposed/mcpnatively since 1.27. So we can delete our bundled MCP middleman entirely and have Codex talk to OV directly.What changes
.mcp.jsonswitched tourl+bearer_token_env_var: "OPENVIKING_API_KEY"+env_http_headersfor the multi-tenant identity headers. URL is a__OPENVIKING_MCP_URL__placeholder; installer renders it fromovcli.conf.url/OPENVIKING_URLat 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:/mcpURL (priority:OPENVIKING_MCP_URL>OPENVIKING_URL/mcp>ovcli.conf.url/mcp>localhost) andsed-renders it into the cached.mcp.json.codex()shell function to~/.zshrc/~/.bashrcthat promotesovcli.conffields into env vars (OPENVIKING_API_KEY,_ACCOUNT,_USER,_AGENT_ID) before exec'ing codex. Mirrors the claude-code installer'sclaude()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.mjsrunning on Codex's bundled Node 22.README +
docs/{en,zh}/agent-integrations/04-codex.mdrewritten:guides/06-mcp-integration.mdinstead of duplicated.codex()shell-function wrapper snippet for manual setup.Plugin version: 0.4.1 → 0.5.0.
What the user gets
npm ciof @modelcontextprotocol/sdk + zod, ~600 LOC TS proxy/mcpdirectlyrecall,store,forget,health)add_resource,forget,glob,grep,health,list,read,search,store)ovcli.confonlyovcli.confonly (shell wrapper promotes to env at launch;.mcp.jsonreferences viabearer_token_env_var, never embeds)Validation
End-to-end on Codex 0.130 against a live OV server:
Tradeoffs / notes
.mcp.json). API key rotation only needs a codex restart (env re-read via the wrapper)./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 callsearch/store/ etc. as MCP tools. Users on OV ≥ 1.27 are unaffected.env_http_headerssilently omits missing env vars — users who don't setOPENVIKING_USERjust don't sendX-OpenViking-User; OV server applies its defaults.Test plan
.mcp.jsonplaceholder__OPENVIKING_MCP_URL__is rendered by installer.mcp.jsonURL matchesovcli.conf.url+/mcp(orOPENVIKING_URLenv override, with priority documented)bearer_token_env_var: "OPENVIKING_API_KEY"producesAuth: Bearer tokenin/mcp, not the OAuth fallback Codex falls back to when no auth is configured/mcpandhealthround-trip worksruntime-common.mjsimport.meta.urlfallback because the launcher is gone)api_keyinovcli.confthen restarting codex picks up the new key without re-install${CODEX_PLUGIN_ROOT}/${CODEX_PLUGIN_DATA}/package.json/node_modules/servers//src/in checked-in plugin