* fix(codex): read the MCP proxy's connection from the hooks' loadConfig
The proxy resolved url, api_key, account and user through the bare
credential chain while taking everything else from loadConfig(). Since
the loader became buildPluginConfig() it adds layers that chain never
sees: ovcli.conf's plugin.codex apiKey/accountId/userId, and ov.conf's
codex.apiKey when ovcli.conf names only the server. With either, hooks
authenticated and every MCP tool call went out without a key.
Codex also hands a stdio MCP server only the env vars .mcp.json lists,
and OPENVIKING_AUTH_MODE was not one of them, so an env-set auth mode
decided the identity headers for hooks but not for MCP calls.
* fix(dsh): forward the resolved auth mode and timeout to the MCP proxy
The proxy runs as a child whose env DSH scrubs, so the parent forwards
what it resolved. It forwarded the endpoint, key, account, user and
peer but not the auth mode or request timeout, so a Cordis patch that
set either configured the in-process runtime and not the MCP calls.
The proxy now also takes its credential source and watched paths from
the resolved config instead of a second credential-chain call, and a
shared test keeps every proxy that ships beside hooks off that chain.
* refactor(shared): one connection resolver for hooks and the MCP proxy
The credential chain lived in two layers. `resolveOpenVikingCredentials()`
could not read ovcli.conf's `plugin.<harness>` keys, the ov.conf harness
fallback or the root-key tail; `buildPluginConfig()` patched those in, and
any caller that used the lower layer alone resolved a different key and
identity than the hooks did.
`resolveConnection(harness, { env, files, hostInput, rootKeyFallback })`
now answers server, key, identity and auth mode in one place, reading only
host input, the environment and the two ~/.openviking files. The hook
loader and `buildProxyConnection()` both consume it, and the old
two-layer entry points (`resolveOpenVikingCredentials`, `resolveAuthMode`,
the credentials.mjs CLI) are gone so a half-resolved chain cannot be
written again.
Behaviour is unchanged for every hook harness (checked field by field
against the previous implementation over thousands of generated file/env
combinations). Two deliberate additions: the portable agent-plugins proxy
now honours ovcli.conf's `plugin` connection keys and ov.conf's harness
section like every other harness, and dsh hands the host's `authMode`
(or `auth_mode`) over as host input, ranking it with the host's endpoint,
key and identity.
* fix(shared): a forced env credential source reads only the environment
`OPENVIKING_CREDENTIAL_SOURCE=env` is documented as "env vars only", but
only the url honoured it: the key, account, user, ovcli.conf's actor peer
and the auth mode still fell through to ovcli.conf, its plugin keys,
ov.conf and the root key when the variable was unset. A process that
exported an empty key to mean "no key" was silently handed whatever the
files held.
Forced to `env`, the connection now reads no file and an unset variable
stays empty; the url defaults to http://127.0.0.1:1933. The `peerId`
setting keeps its own layers. The doctor labels that mode instead of
pointing at files the chain skipped.
* refactor(shared): one proxy-config mapper and one forwarded-env list
Each proxy entrypoint copied a dozen fields out of its loader by hand,
under two sets of names, and the copies had drifted. What the proxy
process must be handed was a second hand-kept list, in Codex's
`.mcp.json` and in its test.
`toMcpProxyConfig(cfg, options)` maps a resolved loader or proxy
connection to the proxy config once. `MCP_PROXY_ENV_VARS` names every
variable that changes what a proxy sends; Codex's `env_vars` is now
checked against it, which adds the missing `OPENVIKING_STATE_DIR`.
* refactor(plugins): every loader takes an env, every proxy exports readProxyConfig(env)
The six MCP proxy entrypoints now reduce to one line: resolve through the
harness's own loader (or `buildProxyConnection` for the hook-less package)
and hand the result to `toMcpProxyConfig`. Every one exports
`readProxyConfig(env)`, and the codex, claude-code, opencode and agent-hook
loaders accept an injected env, so a test can drive a hook and its proxy
from the same inputs without touching process.env.
Mapping through one function fixes what the hand copies had lost: the
Claude Code and DSH proxies never passed `mcpUrl`, so
`OPENVIKING_MCP_URL` moved the hooks and left the tools behind.
The source guard now requires the shared mapper and the exported reader
in every proxy, and `buildProxyConnection` reports its two config paths
instead of a watch list of its own.
* fix(dsh): forward the resolved connection to the MCP proxy
DSH starts its MCP subprocess with the parent's environment minus
credential-shaped names (`/KEY|PASSWORD|SECRET|TOKEN/i`), so the bundle
forwards what it resolved. It forwarded the values but not the mode, and
only the non-empty ones:
- A child that receives `OPENVIKING_URL` runs the chain unpinned. Where
the parent's chain was pinned to an ovcli.conf that names only a url,
the parent sent no key while the proxy fell through to ov.conf's
`server.root_api_key`, so the tools reached the server as root while
the hooks were anonymous.
- `OPENVIKING_ACCOUNT`, `OPENVIKING_USER` and `OPENVIKING_PEER_ID` survive
DSH's scrub, so a value the parent's chain ignored filled the gap in
the child and went out as an identity header.
- With no peer to forward, the proxy derived one from its own launch
directory and sent an actor peer the runtime did not.
`forwardConnectionEnv(connection)` now writes every credential variable,
the empty ones too, with the forced `env` source, so the child reads no
file and resolves exactly the parent's url, MCP url, key, identity, auth
mode and peer. The proxy takes its peer from that environment only.
`buildMcpConfig` moves to `mcp-env.mjs`, which carries no host
dependency, so shared tests can build the child environment without the
DSH bridge.
* test(shared): prove the proxy and the hooks resolve one connection
The existing guards checked shape — that a proxy called the shared
builder — never that it reached the server as the same caller its hooks
did, which is how two harnesses shipped proxies that disagreed with them.
`mcp-hook-parity.test.mjs` runs every harness that ships a proxy beside
hooks through a dozen configurations: ovcli.conf's own fields, its
`plugin.<harness>` and shared plugin keys, ov.conf-only installs, the
pinned fallbacks to a harness key and to the root key, credential and
auth-mode variables, a forced source over stale variables, an explicit
MCP URL, a host's own input, and a workspace file that tries to move the
connection. The hook loader sees the full environment; the proxy sees
only what its host lets through — Codex's `env_vars`, DSH's scrubbed
inheritance plus the forwarded connection, everyone else's full
environment — and the url, key, identity and identity-header switch they
put on the wire must match. Scenarios with a known answer pin it too, and
a coverage check fails when a new proxy or hook client has no row.
The two codex-only proxy tests the matrix now covers are removed.
* docs(plugins): one connection for hooks and MCP, and version bumps
The capability reference, plugin development guide, Agent Plugins and
Codex pages (en/zh), both doctor references and the plugin READMEs now
describe the chain `resolveConnection()` runs: host input first, the
pinned ovcli.conf branch and what still falls through it, the auth mode
reading `OPENVIKING_AUTH_MODE` and the `plugin` keys in every mode, a
forced `env` source reading no file, and the two ways a connection crosses
into an MCP process (Codex's forwarded-variable list, dsh's forwarded
connection). The parity test is registered with the credential tests.
Versions move past both this branch's base and main: claude-code 0.5.2,
codex 0.9.2, agent-hook 0.3.2, opencode 0.3.2, dsh 0.4.3, pi 0.3.2,
agent-plugins 0.1.2.
OpenViking Agent Plugins (Agent Plugins 1.0)
Portable Agent Plugins 1.0 package for OpenViking: long-term semantic memory and context for coding agents.
Agent Plugins 1.0 is a vendor-neutral packaging format for extending AI coding agents, backed by Amazon, Cursor, Microsoft, OpenAI, and Vercel among others. A plugin is a plain directory with a plugin.json manifest, auto-discovered Agent Skills under skills/, and optional MCP server declarations in mcp.json — one package that any conforming client (Cursor, VS Code, Amazon- and OpenAI-side clients, ...) can load the same way. This directory is that package for OpenViking.
What's inside
plugin.json # Agent Plugins 1.0 manifest
mcp.json # stdio MCP server: "openviking"
servers/mcp-proxy.mjs # stdio -> streamable-HTTP proxy to the OV server's /mcp
servers/shared/ # generated from examples/memory-plugin-shared/lib (do not edit)
skills/openviking-memory/SKILL.md # teaches the model the recall + persist loop
skills/ov-memory-troubleshoot/ # read-only extraction troubleshooting
plugin.test.mjs # node --test conformance checks
Use ov-memory-troubleshoot to trace backward from a memory file to its archive diff and, when needed, session messages. Diagnosis is read-only.
Zero npm dependencies; the proxy and tests run on the Node.js standard library (Node 18+ for global fetch).
Install
- Have an OpenViking server reachable (see the quickstart); default local endpoint is
http://127.0.0.1:1933. - Point your Agent-Plugins-conforming client at this directory (each client has its own install command or plugin directory; consult its docs). The client will:
- register the
openvikingMCP server frommcp.json— it runsnode <plugin>/servers/mcp-proxy.mjsover stdio; - discover the
openviking-memoryandov-memory-troubleshootskills fromskills/.
- register the
- Configure credentials (next section) and start a session. The model gains
find/search/read/remember/writeand the other OpenViking MCP tools. Usesearchwithmode="context"for server-assembled context.
Why a stdio proxy instead of a streamable-http entry
OpenViking already speaks streamable HTTP at /mcp, but a streamable-http entry in mcp.json cannot work portably: the server URL is per-deployment (localhost for one user, a remote endpoint for another), and the Agent Plugins spec forbids credentials in the static headers map. The stdio proxy solves both — it resolves the URL and API key at runtime from the same local sources as the ov CLI, injects them per request, and forwards JSON-RPC over streamable HTTP unchanged.
Credential resolution
Highest to lowest priority (same chain as the ov CLI and the other OpenViking plugins):
- Environment variables:
OPENVIKING_URL(orOPENVIKING_BASE_URL),OPENVIKING_API_KEY(orOPENVIKING_BEARER_TOKEN),OPENVIKING_ACCOUNT,OPENVIKING_USER,OPENVIKING_PEER_ID ~/.openviking/ovcli.conf(url,api_key,account,user, then theplugin.agent_pluginsandpluginkeys) — override the path withOPENVIKING_CLI_CONFIG_FILE~/.openviking/ov.confagent_pluginssection, then itsserversection (urlorhost/port,root_api_key) — override the path withOPENVIKING_CONFIG_FILE- Defaults:
http://127.0.0.1:1933, no auth (local mode)
Config file changes are picked up by the running proxy without a restart. Debugging: set OPENVIKING_DEBUG=1 to log JSON lines to ~/.openviking/logs/agent-plugins.log (path override: OPENVIKING_DEBUG_LOG).
Scope: what this package does and doesn't do
This package is the portable recall + write surface: skills plus MCP tools, driven by the model. Agent Plugins 1.0 deliberately excludes hooks, commands, and agents, so automatic conversation capture and automatic pre-prompt recall are out of scope here — the skills/openviking-memory skill instead teaches the model to recall at task start and persist durable facts via remember/write itself.
If your harness has a hook system, prefer the dedicated plugin — hook-driven recall and capture cost no tool calls and don't depend on the model choosing to remember. One installer covers Claude Code, Codex, Cursor, TRAE / TRAE CN, ZCode, OpenCode, and pi; it prompts for language, harnesses, download source, and credentials, and is idempotent:
bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/memory-plugin-shared/install.sh)
# GitHub hard to reach? Same installer from the Volcengine TOS mirror:
bash <(curl -fsSL https://ovrelease.tos-cn-beijing.volces.com/memory-plugin-shared/install.sh)
- claude-code-memory-plugin (Claude Code)
- codex-memory-plugin (Codex)
- opencode-plugin (OpenCode)
- agent-hook-plugin (Cursor, TRAE, TRAE CN, ZCode), ...
Use this Agent Plugins package for harnesses with no hooks, or when you want one package that loads across many clients.
Per the spec, client-specific integrations may later be embedded in this package under reverse-domain namespaced directories (e.g. com.example.client/) or the manifest's extensions object without breaking other clients.
Development
node --test agent-plugins/plugin.test.mjs
servers/shared/*.mjs are generated, verbatim copies from examples/memory-plugin-shared/lib — do not edit them here. This directory is a target of the shared-lib sync script, so refresh them with:
node examples/memory-plugin-shared/sync.mjs
examples/memory-plugin-shared/sync.test.mjs fails if they drift, and pins this package to the connection half of the shared runtime: servers/mcp-proxy.mjs resolves everything through buildProxyConnection() in shared/credentials.mjs — the same resolveConnection() every other plugin's hooks and proxy use — so the hook-tuning knobs — which this spec has no hooks to run — never enter the bundle.
Both test files run in CI via .github/workflows/pr.yml.