Skip to content

fix(openclaw): register MCP sidecar on install so ctx_* tools surface to agent#339

Open
murataslan1 wants to merge 3 commits intomksglu:nextfrom
murataslan1:fix/openclaw-register-mcp-sidecar
Open

fix(openclaw): register MCP sidecar on install so ctx_* tools surface to agent#339
murataslan1 wants to merge 3 commits intomksglu:nextfrom
murataslan1:fix/openclaw-register-mcp-sidecar

Conversation

@murataslan1
Copy link
Copy Markdown

@murataslan1 murataslan1 commented Apr 24, 2026

Summary

Before this PR, installing context-mode on OpenClaw 2026.4.22 produced a silent registration failure: openclaw plugins list showed the plugin as loaded, openclaw doctor reported zero errors, scripts/ctx-debug.sh mostly passed — but none of the ctx_* tools surfaced to the agent. The model even refused an explicit request:

"I can't use ctx_execute here because that tool isn't available in this session; if you want, I can do the same calculation with exec instead."

Cause: context-mode's ctx_* tools live in src/server.ts, exposed over stdio MCP (11× server.registerTool + StdioServerTransport). Other adapters (Claude Code, Codex, Cursor) spawn server.bundle.mjs via their platform's mcpServers config. The OpenClaw install script writes plugins.allow / plugins.entries to openclaw.json but never adds the mcp.servers.context-mode entry, so the gateway never spawns the sidecar and the agent never sees the tools.

This PR fills in that step.

Changes

  • scripts/lib/register-openclaw-config.mjs (new) — idempotent helper extracted from the install script. Writes plugins.allow, plugins.entries, cleans legacy plugins.load.paths (existing behavior), and additionally registers mcp.servers.context-mode pointing at <pluginRoot>/server.bundle.mjs. Re-running is a no-op; stale server paths are refreshed.
  • scripts/install-openclaw-plugin.sh — step 5 delegates to the new helper (previously inline node -e).
  • tests/plugins/openclaw.test.ts — 5 new unit tests against the helper: MCP entry presence, idempotency, stale-path refresh, plugins.allow/entries contract, legacy plugins.load.paths cleanup.
  • docs/adapters/openclaw.md — new troubleshooting entry documenting the MCP-sidecar requirement and the manual openclaw mcp set recovery command.

Verification

$ npm run typecheck
> tsc --noEmit
(clean)

$ npx vitest run tests/plugins/openclaw.test.ts tests/adapters/openclaw.test.ts
 Test Files  2 passed (2)
      Tests  117 passed (117)     # baseline 112 + 5 new

Live check on OpenClaw 2026.4.22 + context-mode 1.0.89 after the fix — the agent's tool inventory now includes the full ctx_* surface (OpenClaw prefixes MCP-sourced tool names with the server name):

context-mode__ctx_batch_execute
context-mode__ctx_doctor
context-mode__ctx_execute
context-mode__ctx_execute_file
context-mode__ctx_fetch_and_index
context-mode__ctx_index
context-mode__ctx_insight
context-mode__ctx_purge
context-mode__ctx_search
context-mode__ctx_stats
context-mode__ctx_upgrade

Full debugging writeup

gist — "OpenClaw MCP sidecar gap: debugging walkthrough"

Covers initial (incorrect) diagnosis, cross-check with @benzntech on LinkedIn + his follow-up on #45, the src/server.ts trace that surfaced the 11 server.registerTool calls, and the final resolution path.

Relation to existing work

Full test-suite caveats (environment-dependent)

In my local environment, npm test surfaces 5 failures in tests/hooks/integration.test.ts → Security Policy Enforcement, and git stash-ing this PR and re-running reproduces the same 5 failures on main — so they are not caused by this change on my machine. However, an independent review of the PR via the OpenAI Codex CLI (summary linked in a follow-up comment) could not reproduce those in a clean environment; that environment only hit a Rust toolchain not configured failure, also present on both branch and main. So whatever is going on in tests/hooks/integration.test.ts locally appears to be environment-specific, not a property of this PR or of main. CI on the project's own infra is the authoritative signal.

Scope discipline

No changes outside the OpenClaw adapter, the OpenClaw install script, and OpenClaw docs. Other adapter hooks, src/server.ts, the session layer, and all non-OpenClaw tests are untouched.

Open questions for @ipedro / @mksglu (non-blocking)

  1. Should the plugin itself spawn the sidecar from register() rather than relying on openclaw.json config? If yes, what's the preferred api.* call?
  2. configs/openclaw/AGENTS.md guidance uses unprefixed ctx_execute rather than the context-mode__ctx_execute name that the agent actually sees. Prefixless names in routing instructions may mildly confuse the model — worth a pass?
  3. Is there a way to detect / warn when the OpenClaw version predates the openclaw mcp set surface?

Copilot AI review requested due to automatic review settings April 24, 2026 15:38
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 fixes OpenClaw installs where the plugin loads successfully but the agent never sees the ctx_* tools, by ensuring the OpenClaw runtime config registers the context-mode MCP sidecar (mcp.servers.context-mode) that spawns server.bundle.mjs.

Changes:

  • Adds a new idempotent helper to mutate openclaw.json (including registering mcp.servers.context-mode).
  • Updates the OpenClaw install script to delegate config mutation to the helper.
  • Adds unit tests and docs troubleshooting guidance for missing ctx_* tools.

Reviewed changes

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

File Description
scripts/lib/register-openclaw-config.mjs New helper that mutates openclaw.json and registers the MCP sidecar.
scripts/install-openclaw-plugin.sh Uses the new helper instead of an inline node -e config mutation.
tests/plugins/openclaw.test.ts Adds unit tests for the new helper’s behavior (MCP entry, idempotency, stale refresh, cleanup).
docs/adapters/openclaw.md Documents the MCP-sidecar requirement and manual recovery via openclaw mcp set.

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

Comment on lines +54 to +62
const serverBundle = `${pluginRoot}/server.bundle.mjs`;
const existing = servers["context-mode"];
const needsWrite =
!existing ||
existing.command !== "node" ||
!Array.isArray(existing.args) ||
existing.args[0] !== serverBundle;
if (needsWrite) {
servers["context-mode"] = { command: "node", args: [serverBundle] };
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

serverBundle is built via string interpolation (${pluginRoot}/server.bundle.mjs). If pluginRoot is relative or has a trailing slash, this can write a non-absolute or malformed path (e.g., //server.bundle.mjs), despite the docs/tests expecting an absolute path. Consider normalizing with resolve(pluginRoot, "server.bundle.mjs") (or join) and comparing against the normalized value in needsWrite.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +34
const idx = paths.indexOf(pluginRoot);
if (idx !== -1) {
paths.splice(idx, 1);
if (!paths.length) delete load.paths;
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

Legacy cleanup only removes the first occurrence of pluginRoot from plugins.load.paths via indexOf/splice. If the config accumulated duplicates (which can happen with repeated installs), extra entries remain and may continue to cause duplicate registration. Consider removing all matching entries (e.g., filter) before deciding whether to delete load.paths.

Suggested change
const idx = paths.indexOf(pluginRoot);
if (idx !== -1) {
paths.splice(idx, 1);
if (!paths.length) delete load.paths;
const filteredPaths = paths.filter((p) => p !== pluginRoot);
if (filteredPaths.length !== paths.length) {
if (filteredPaths.length) load.paths = filteredPaths;
else delete load.paths;

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +55
// 2. Add to plugins.allow (idempotent)
const allow = (plugins.allow ??= []);
if (!allow.includes("context-mode")) allow.unshift("context-mode");

// 3. Add to plugins.entries (idempotent)
const entries = (plugins.entries ??= {});
if (!entries["context-mode"]) entries["context-mode"] = { enabled: true };

// 4. Register MCP sidecar so OpenClaw spawns server.bundle.mjs and surfaces
// ctx_* tools to the agent. Without this entry the plugin loads but its
// tools never reach the agent's tool list (confirmed against OpenClaw
// 2026.4.22: context-mode 1.0.89 plugin "loaded" but no ctx_* tools
// visible until mcp.servers.context-mode was set).
const mcp = (cfg.mcp ??= {});
const servers = (mcp.servers ??= {});
const serverBundle = `${pluginRoot}/server.bundle.mjs`;
const existing = servers["context-mode"];
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

This function assumes plugins.allow is an array and plugins.entries/mcp.servers are plain objects. If a user has a malformed openclaw.json (e.g., plugins.allow as a string), allow.includes(...) / property writes will throw with a confusing error. Consider validating these shapes and throwing a targeted error (or coercing to defaults) before mutating.

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +23
try {
cfg = JSON.parse(readFileSync(runtimePath, "utf8"));
} catch (e) {
throw new Error(`Failed to parse ${runtimePath} — is it valid JSON? (${e.message})`);
}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The JSON parse error path uses e.message, but catch (e) can receive non-Error values in JS. Accessing e.message can itself throw (masking the original parse failure). Safer pattern: derive the message via e instanceof Error ? e.message : String(e) (both here and in the CLI catch below).

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +90
} catch (e) {
console.error(` ✗ ${e.message}`);
process.exit(1);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

Same issue in the CLI error handler: e.message is not safe if a non-Error is thrown. Prefer e instanceof Error ? e.message : String(e) so the script always prints a useful error and exits cleanly.

Copilot uses AI. Check for mistakes.
@murataslan1
Copy link
Copy Markdown
Author

End-to-end verification — the patched install script surfaces ctx_* to the agent ✅

Re-ran the modified install script from this branch against a real OpenClaw state dir (~/.openclaw) after the commit was pushed:

$ git branch --show-current
fix/openclaw-register-mcp-sidecar

$ OPENCLAW_STATE_DIR="$HOME/.openclaw" bash scripts/install-openclaw-plugin.sh
...
→ registering in /Users/murat/.openclaw/openclaw.json...
  registered mcp.servers.context-mode → /Users/murat/Desktop/context-mode/server.bundle.mjs
  plugins.allow: ["context-mode"]
...
✓ done — context-mode plugin installed and active

The new registered mcp.servers.context-mode → … log line is emitted by the helper introduced in this PR. The previous config had the entry pointing at a different path (/opt/homebrew/…); the helper idempotently refreshed it to the clone path — the updates a stale MCP server path when pluginRoot changes unit test reproduced against a real install.

openclaw mcp show context-mode after install:

{
  "command": "node",
  "args": ["/Users/murat/Desktop/context-mode/server.bundle.mjs"]
}

After openclaw gateway restart, the agent's tool inventory:

$ openclaw agent --agent main \
    --message "Reply with exact tool names starting with 'context-mode__', one per line, no explanation." \
    --thinking low

context-mode__ctx_batch_execute
context-mode__ctx_doctor
context-mode__ctx_execute
context-mode__ctx_execute_file
context-mode__ctx_fetch_and_index
context-mode__ctx_index
context-mode__ctx_insight
context-mode__ctx_purge
context-mode__ctx_search
context-mode__ctx_stats
context-mode__ctx_upgrade

All 11 ctx_* tools surface to the agent. The end-to-end chain works on a fresh run of the modified install script.

Full walkthrough (with pre/post state diffs) has been appended to the companion gist as §11: openclaw-mcp-gap-debugging.md § 11.

Pre-existing install-script gotcha surfaced during this run (NOT introduced by this PR)

The install script's step 1 runs npm rebuild better-sqlite3 using whatever Node is picked up from npm config get prefix. On my machine that's Node 22; the LaunchAgent gateway runs under Node 24. Result: the binary was built against NODE_MODULE_VERSION 127 while the gateway requires 137, and plugin load failed until a manual nvm use 24 && npm rebuild better-sqlite3. This is orthogonal to the MCP-sidecar registration fix — possibly worth a separate follow-up issue to make the rebuild pick up the gateway's Node.

@murataslan1
Copy link
Copy Markdown
Author

UI QA — three prompts in the Control UI dashboard, plus a follow-up commit

Before asking for review, I ran three prompts in the OpenClaw dashboard chat to stress what happens in practice (openai-codex/gpt-5.4, single fresh session, post-install-script state from the earlier verification comment).


Prompt A — explicit tool name, non-cacheable output

Use context-mode__ctx_execute to run this Python and return ONLY the printed output:
import hashlib, time
print(hashlib.sha1(str(time.time_ns()).encode()).hexdigest()[:12])
  • Tool trace: context-mode__ctx_execute (single call)
  • Tokens: ↑13.3k ↓62 R15.9k 7% ctx
  • Result: 4c925e2afbca (a fresh SHA-1 of time.time_ns() — can't be cached, so the subprocess really ran)
  • Verdict: ✅ explicit invocation path is live end-to-end.

Prompt B — natural language, small target (2KB package.json)

Fetch https://raw.githubusercontent.com/mksglu/context-mode/main/package.json,
index it, and tell me the version field value.
  • Tool trace (in order):
    Tool call   context-mode__ctx_fetch_and_index
    Tool output context-mode__ctx_fetch_and_index
    Tool call   web_fetch
    Tool output web_fetch
    
  • Tokens: ↑366 ↓82 R28.9k 0% ctx
  • Result: 1.0.89 (correct)
  • "What tool did you use?" follow-up: web_fetch (the last one)
  • Verdict: ⚠️ partial auto-routing. AGENTS.md guidance made the model reach for context-mode__ctx_fetch_and_index first — unprompted — but it then redundantly fell back to web_fetch.

Prompt C — natural language, large target (58KB README.md)

Fetch https://raw.githubusercontent.com/mksglu/context-mode/main/README.md,
index it, then find sections that mention "context window" and reply with
the count of matching sections only. No explanation.
  • Tool trace:
    Tool call   context-mode__ctx_fetch_and_index
    Tool output context-mode__ctx_fetch_and_index
    Tool call   web_fetch
    Tool output web_fetch
    Tool call   context-mode__ctx_execute
    Tool output context-mode__ctx_execute
    
  • Tokens: ↑6.4k ↓475 R94.7k 3% ctx
  • Result: 4 (correct)
  • Verdict: ⚠️ same pattern as B. context-mode__ctx_fetch_and_index fired, "think in code" (via ctx_execute) worked, but ctx_search — the intended follow-up to fetch_and_index — was never invoked; the model dragged the raw README in via web_fetch and counted with ctx_execute instead.

Root cause — AGENTS.md prefix mismatch

$ grep -c 'context-mode__' configs/openclaw/AGENTS.md
0
$ grep -cE 'ctx_[a-z_]+' configs/openclaw/AGENTS.md
14

AGENTS.md referred to the tools by their unprefixed names (ctx_execute, ctx_search, …) 14 times. The agent sees them as context-mode__ctx_* because OpenClaw's MCP aggregator prefixes server names onto the tools it surfaces. The model mostly bridged the gap on its own (the explicit invocation in Prompt A worked; context-mode__ctx_fetch_and_index fired in B and C). But under pressure — short prompt, small target, a built-in with a shorter name available — it defaulted to web_fetch / exec.

Follow-up commit on this PR — 5fc3ac6

Rather than punt this to a separate issue, I added a second commit to the branch that rewrites the 14 mentions in configs/openclaw/AGENTS.md to use the names the agent actually sees:

perl -i -pe 's/\bctx_/context-mode__ctx_/g' configs/openclaw/AGENTS.md
# 14 replacements, 0 bare ctx_ remaining

Scope is still OpenClaw-only — configs/pi/AGENTS.md and every other adapter's AGENTS.md are untouched, because those platforms don't apply the server-name prefix.

Commits now on the PR:

Commit Purpose
94b4fca register MCP sidecar on install so ctx_* tools surface to agent
5fc3ac6 prefix ctx_* names in AGENTS.md so the model invokes MCP variants

The second commit is a system-prompt text-only change — tests and typecheck are unchanged (still tests/plugins/openclaw.test.ts + tests/adapters/openclaw.test.ts → 117/117, typecheck clean).

Next-step verification I haven't done yet

Re-running Prompts B and C against the second commit should show web_fetch dropping out and context-mode__ctx_search being called where AGENTS.md now explicitly prescribes the fetch_and_index → search pattern. Happy to post results if that would help review.

Consolidated evidence

Full walkthrough (with all commands, grep results, screenshots described textually): updated gist — §12 UI QA report.

cc @benzntech (the LinkedIn cross-check on the MCP-sidecar reasoning led directly to these findings).

murataslan1 added a commit to murataslan1/context-mode that referenced this pull request Apr 24, 2026
Addressing the review points from Codex's independent CLI review of PR mksglu#339:

1. The helper previously did a full reset of mcp.servers["context-mode"] on
   every path refresh, which would silently drop user-supplied custom fields
   (env, cwd, timeout, anything OpenClaw adds in a future release). It now
   spreads the existing entry and only overwrites the two fields the helper
   owns (command + args).

2. Added two edge-case tests in tests/plugins/openclaw.test.ts:
   - "preserves unrelated fields on the existing mcp.servers entry when
     refreshing the path" — seeds env/cwd/timeout on the old entry, bumps
     pluginRoot, asserts all three survive alongside the new args[0].
   - "throws a useful error when the runtime config is not valid JSON" —
     exercises the previously-uncovered parse-error path.

Test count 117/117 → 119/119 on `tests/plugins/openclaw.test.ts` +
`tests/adapters/openclaw.test.ts`. Typecheck still clean.

Scope unchanged — still inside OpenClaw adapter/install/tests.
@murataslan1
Copy link
Copy Markdown
Author

Independent Codex CLI review — results + follow-up commit 3766cca

Before flagging this for human review I ran the PR through codex-cli 0.122.0 as an independent reviewer. Full verdict: ship, no blocking findings. The review verified the two-commit scope, confirmed the runtime tool surface (11 context-mode__ctx_* names visible to the agent), and re-ran both the targeted and full test suites.

Two items came back as legitimate weaknesses, and I've addressed both in commit 3766cca:

1. Full-reset of mcp.servers.context-mode could drop user/OpenClaw-added fields.
The previous helper wrote { command: "node", args: [serverBundle] } wholesale when the entry needed refreshing. If someone had manually set env, cwd, timeout, or if a future OpenClaw release adds its own optional keys, the install script would silently erase them on the next run. The helper now spreads the existing entry and only overwrites the two fields it owns:

const base = existing && typeof existing === "object" ? existing : {};
servers["context-mode"] = { ...base, command: "node", args: [serverBundle] };

2. The try/catch around JSON.parse was never exercised by a test. Added in the same commit.

New tests in tests/plugins/openclaw.test.ts (now 119/119, +2 over the previous 117):

  • preserves unrelated fields on the existing mcp.servers entry when refreshing the path — seeds env/cwd/timeout on the old entry, bumps pluginRoot, asserts all three survive alongside the new args[0].
  • throws a useful error when the runtime config is not valid JSON — exercises the parse-error path.

PR body corrected

Codex couldn't reproduce the "5 failures in tests/hooks/integration.test.ts" I claimed in my original PR body. In their environment the only failure on both branch and main was a missing Rust toolchain. So those 5 failures are apparently specific to my local setup, not a property of main. The PR body has been edited to reflect that (the section is now "Full test-suite caveats (environment-dependent)" rather than a confident "pre-existing on main" claim). CI on the project's infra is the authoritative signal.

Remaining architectural question (unchanged, not part of this PR)

The review left open the same question I flagged at the bottom of the PR body — should the plugin itself spawn the MCP sidecar from register() rather than relying on openclaw.json config? — and agreed it's worth a separate maintainer discussion rather than scope-creeping this PR.

Current state of PR #339

Commit Purpose
94b4fca register MCP sidecar on install so ctx_* tools surface to agent
5fc3ac6 prefix ctx_* names in AGENTS.md so the model invokes MCP variants
3766cca preserve unrelated MCP server fields; add edge-case tests
  • Typecheck: clean
  • tests/plugins/openclaw.test.ts + tests/adapters/openclaw.test.ts: 119/119
  • Scope: still OpenClaw-only (adapter docs / install / tests / configs/openclaw)

Full Codex transcript + updated walkthrough lives in the gist: §13 — Independent Codex review follow-up.

@mksglu mksglu changed the base branch from main to next April 24, 2026 17:40
@mksglu
Copy link
Copy Markdown
Owner

mksglu commented Apr 24, 2026

@murataslan1 seems has a conflict :)

@murataslan1
Copy link
Copy Markdown
Author

Post-5fc3ac6 validation — web_fetch fallback is gone ✅

Restarted the gateway after commit 5fc3ac6 (so the plugin re-reads the newly-prefixed configs/openclaw/AGENTS.md), opened a fresh dashboard session, and re-ran the same Prompt C from the UI QA comment.

Prompt C (verbatim, unchanged):

Fetch https://raw.githubusercontent.com/mksglu/context-mode/main/README.md,
index it, then find sections that mention "context window" and reply with
the count of matching sections only. No explanation.

Tool-call chain — before vs after the prefix fix:

Turn Before 5fc3ac6 (old AGENTS.md) After 5fc3ac6 (prefixed AGENTS.md)
1 context-mode__ctx_fetch_and_index context-mode__ctx_fetch_and_index
2 web_fetch ⚠ (redundant)
3 context-mode__ctx_execute context-mode__ctx_execute
3 tool calls 2 tool calls
Model reply 4 4

Headline: web_fetch is gone. The redundant fallback that showed up in both Prompt B (small target) and Prompt C (large target) during the UI QA run no longer fires once AGENTS.md refers to the tools by the names the agent actually sees (context-mode__ctx_*). That was the whole point of 5fc3ac6.

Running-context footprint dropped: R94.7k → R37.1k on the same prompt. Apples-to-oranges across sessions so treat this as directional, not a precise delta — but the direction is right: no web_fetch means less raw web content threading through the conversation context.

Observation on ctx_search: the model still preferred context-mode__ctx_execute for the count instead of context-mode__ctx_search. That's consistent with AGENTS.md's "Think in Code" mandate — for a precise count, deterministic code is a reasonable choice over BM25-scored top-K search, and both are legitimate. Not a regression. If you ever want to push the model toward ctx_search for questions like this, a worked example of the fetch_and_index → search pattern in AGENTS.md would be the lever; that's a follow-up polish rather than something in this PR's scope.

Where this leaves PR #339

All three commits validated end-to-end on OpenClaw 2026.4.22 + context-mode 1.0.89:

Commit What it fixes Verified by
94b4fca Tools surface to the agent at all openclaw plugins list + tool inventory (11 context-mode__ctx_* names)
5fc3ac6 Routing no longer falls back to web_fetch This comment (3 tool calls → 2, chain above)
3766cca Install script no longer overwrites user-added MCP server fields tests/plugins/openclaw.test.ts (119/119)

Full walkthrough updated in the companion gist: §14 — Post-prefix validation.

murataslan1 added a commit to murataslan1/context-mode that referenced this pull request Apr 24, 2026
Addressing the review points from Codex's independent CLI review of PR mksglu#339:

1. The helper previously did a full reset of mcp.servers["context-mode"] on
   every path refresh, which would silently drop user-supplied custom fields
   (env, cwd, timeout, anything OpenClaw adds in a future release). It now
   spreads the existing entry and only overwrites the two fields the helper
   owns (command + args).

2. Added two edge-case tests in tests/plugins/openclaw.test.ts:
   - "preserves unrelated fields on the existing mcp.servers entry when
     refreshing the path" — seeds env/cwd/timeout on the old entry, bumps
     pluginRoot, asserts all three survive alongside the new args[0].
   - "throws a useful error when the runtime config is not valid JSON" —
     exercises the previously-uncovered parse-error path.

Test count 117/117 → 119/119 on `tests/plugins/openclaw.test.ts` +
`tests/adapters/openclaw.test.ts`. Typecheck still clean.

Scope unchanged — still inside OpenClaw adapter/install/tests.
@murataslan1 murataslan1 force-pushed the fix/openclaw-register-mcp-sidecar branch from 3766cca to cc9f97b Compare April 24, 2026 17:58
… to agent

context-mode's ctx_* tools live in server.bundle.mjs and are exposed over
stdio MCP. Other adapters (Claude Code, Codex, Cursor) spawn the bundle via
their platform's mcpServers config. The OpenClaw install script wrote
plugins.allow / plugins.entries to openclaw.json but never added
mcp.servers.context-mode, so the gateway never spawned the sidecar and
the agent never saw the ctx_* tool list — while openclaw plugins list,
openclaw doctor, and scripts/ctx-debug.sh all reported healthy state.

Changes
- scripts/lib/register-openclaw-config.mjs (new): extracts step 5 of the
  install script into a testable helper. Writes plugins.allow / .entries /
  cleans legacy plugins.load.paths (existing behavior) and additionally
  registers mcp.servers.context-mode pointing at
  <pluginRoot>/server.bundle.mjs. Idempotent: re-running is a no-op;
  stale server paths are refreshed.
- scripts/install-openclaw-plugin.sh: step 5 delegates to the helper.
- tests/plugins/openclaw.test.ts: 5 unit tests for the helper (MCP entry
  presence, idempotency, stale-path refresh, plugins.allow/entries contract,
  legacy plugins.load.paths cleanup).
- docs/adapters/openclaw.md: new troubleshooting entry documenting the MCP
  sidecar requirement and the manual openclaw mcp set recovery command.

Verification
- npm run typecheck: clean
- vitest tests/plugins/openclaw.test.ts tests/adapters/openclaw.test.ts:
  117/117 pass (baseline 112 + 5 new)
- Live: on OpenClaw 2026.4.22 + context-mode 1.0.89, after the fix the
  agent tool inventory includes context-mode__ctx_execute,
  context-mode__ctx_search, context-mode__ctx_fetch_and_index, and the
  rest of the ctx_* surface (OpenClaw prefixes MCP-sourced tools with
  the server name).

Debugging walkthrough (initial wrong diagnosis, cross-check with
@benzntech, server.ts trace that surfaced the registerTool calls,
final resolution):
https://gist.github.com/murataslan1/cd7b27577fcb535d56fe318c2339b400

Companion to issue mksglu#45 (follow-up comment:
mksglu#45 (comment)).

Pre-existing test failures in tests/hooks/integration.test.ts (Security
Policy Enforcement) are present on main too (reproduced by git-stashing
this PR and re-running), so they are not caused by this change. They
appear related to the PreToolUse relaxation commits (415ce57, 2731ca2,
ece3abb) and are out of scope here.

Scope is limited to OpenClaw adapter files and install path; hooks,
src/server.ts, the session layer, and all non-OpenClaw adapter tests
are untouched.
…CP variants

Complements the previous commit. With the MCP sidecar registered, OpenClaw
surfaces the plugin's tools as context-mode__ctx_* (server-name prefix is
automatically applied by OpenClaw's MCP aggregator). The routing guidance
injected via AGENTS.md still referenced the unprefixed names (ctx_execute,
ctx_search, ...), which left the model to bridge the naming gap on its own.

UI QA on OpenClaw 2026.4.22 + context-mode 1.0.89 confirmed this was costly
in practice: the model would invoke context-mode__ctx_fetch_and_index first
(good), but then redundantly fall back to the built-in web_fetch for the
raw content, and skip context-mode__ctx_search entirely in favor of a
web_fetch + ctx_execute combo. The unprefixed guidance made the built-ins
look like the closer match when under pressure.

This commit rewrites all 14 ctx_* mentions in configs/openclaw/AGENTS.md
to use the context-mode__ctx_* form the model actually sees. Sanity check:

  $ grep -c 'context-mode__ctx_' configs/openclaw/AGENTS.md   # 14
  $ grep -cE '\bctx_[a-z]' configs/openclaw/AGENTS.md         # 0

Scope is still OpenClaw-only — configs/pi/AGENTS.md and every other
adapter's AGENTS.md are untouched, because those platforms don't apply
the server-name prefix.

Tests unchanged; this is a system-prompt text-only change. typecheck and
the full tests/plugins/openclaw.test.ts + tests/adapters/openclaw.test.ts
suite still pass (117/117).
Addressing the review points from Codex's independent CLI review of PR mksglu#339:

1. The helper previously did a full reset of mcp.servers["context-mode"] on
   every path refresh, which would silently drop user-supplied custom fields
   (env, cwd, timeout, anything OpenClaw adds in a future release). It now
   spreads the existing entry and only overwrites the two fields the helper
   owns (command + args).

2. Added two edge-case tests in tests/plugins/openclaw.test.ts:
   - "preserves unrelated fields on the existing mcp.servers entry when
     refreshing the path" — seeds env/cwd/timeout on the old entry, bumps
     pluginRoot, asserts all three survive alongside the new args[0].
   - "throws a useful error when the runtime config is not valid JSON" —
     exercises the previously-uncovered parse-error path.

Test count 117/117 → 119/119 on `tests/plugins/openclaw.test.ts` +
`tests/adapters/openclaw.test.ts`. Typecheck still clean.

Scope unchanged — still inside OpenClaw adapter/install/tests.
@murataslan1 murataslan1 force-pushed the fix/openclaw-register-mcp-sidecar branch from cc9f97b to 3eec67d Compare April 24, 2026 18:15
@murataslan1
Copy link
Copy Markdown
Author

@mksglu — my bad, the earlier rebase went onto main but this PR targets next. Just rebased cleanly onto origin/next with --onto (my three commits only, no stats.json pickup). Mergeable now.

Hashes: aacdaf5, f90c98d, 3eec67d. 119/119 tests, typecheck clean.

@ipedro — thanks for the first read. When you're good, we're ready on our side for whichever release cycle fits. No rush.

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.

3 participants