Skip to content

OpenCode plugin

@ringzero/opencode-mcp-guard hooks OpenCode’s tool.execute.before / tool.execute.after lifecycle events: every MCP tool call is checked against your policy and written to the usage audit log. In enforce mode, dangerous or unapproved calls are blocked before they run.

Add the plugin to your project’s opencode.json (or the global ~/.config/opencode/opencode.json) — OpenCode installs it automatically at startup:

opencode.json
{
"plugin": ["@ringzero/opencode-mcp-guard"]
}
  1. Make sure a policy file is reachable by walking up from the project directory — ringzero-mcp init bootstraps one from your current opencode.json servers.

  2. Connect your org: org id (Settings → General) as organization in the policy; API key (Settings → API keys, scope mcp:upload) in RINGZERO_API_KEY if you sync usage. Local-only works without either.

  3. Restart OpenCode. The guard loads the policy once at startup and reads your opencode.json[c] mcp block to learn which servers exist.

The same environment switches as the CLI apply: RINGZERO_MCP_MODE overrides the policy mode, RINGZERO_MCP_DISABLE=1 is the kill switch (allow everything, still audit), RINGZERO_MCP_AUDIT_DIR moves the log.

When a MCP Registry catalog is present, the plugin applies a project-scope sync at startup — org-approved servers appear in opencode.json without anyone running a command. Changes to opencode.json take effect on the next restart (the plugin says so on stderr when it applied any). Conflicts with hand-added servers are reported, never overwritten; failures are fail-open; opt out with RINGZERO_MCP_REGISTRY_AUTOSYNC=0.

OpenCode names MCP tools <server>_<tool> — the guard matches tool names against the server names configured in your opencode.json[c] mcp block (longest name wins), so built-in tools like bash or edit are never touched. Because the guard knows each server’s source from the config, the allowlist matches by source glob or by name — unlike the Claude Code hook, name-less allow entries work here.

  • mode: "warn" — nothing is blocked; policy violations are audited with decision: "warn" and a stderr note.
  • mode: "enforce" — a matching dangerous pattern, or an unapproved server when runtime.blockUnapproved is true, makes the guard throw, which stops the tool call. The agent sees RingZero policy: server 'rogue' is not on the org allowlist (…) and can route around it or surface it to the dev.
  • Internal guard errors (unreadable policy, audit failures) always fail open — logged to stderr, call allowed.

Records land in the same JSONL log as every other harness, with "harness": "opencode":

{
"ts": "2026-07-26T15:22:47.019Z",
"org": "org_9f3k",
"harness": "opencode",
"event": "pre",
"sessionId": "ses_8f2…",
"server": "github",
"tool": "create_issue",
"decision": "allow",
"rule": null,
"argsSha256": "b81d3c02…",
"argsBytes": 388,
"args": null,
"cwd": "/work/acme-web",
"policyPath": "/work/acme-web/.ringzero/mcp-policy.json",
"policyMode": "enforce"
}

tool.execute.before produces the pre record (with the decision); tool.execute.after produces the post record. Arguments are hashed by default — see privacy defaults.

  • MCP tool identification is a naming heuristic; if OpenCode changes its <server>_<tool> scheme, update the plugin (the match lives in one function and is covered by tests).
  • The policy is loaded at startup — restart OpenCode after editing it.
WhereSettingEffect
opencode.json"plugin": ["@ringzero/opencode-mcp-guard"]Loads the guard
.ringzero/mcp-policy.jsonmode, allow, runtime.*, audit.*Full behavior — see Policy & allowlist
envRINGZERO_MCP_MODE, RINGZERO_MCP_DISABLE, RINGZERO_MCP_AUDIT_DIR, RINGZERO_MCP_LOG_ARGSPer-environment overrides