Skip to content

fix(agents): restore Claude CLI OpenClaw MCP loopback bridge#60714

Merged
steipete merged 1 commit into
mainfrom
temp/landpr-35676
Apr 4, 2026
Merged

fix(agents): restore Claude CLI OpenClaw MCP loopback bridge#60714
steipete merged 1 commit into
mainfrom
temp/landpr-35676

Conversation

@steipete

@steipete steipete commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Replacement land for #35676 by @mylukin.

Summary

  • add an in-process loopback MCP HTTP server for background Claude CLI runs
  • reuse shared gateway-scoped tool resolution so MCP and /tools/invoke honor the same session/account/channel policy filtering
  • only inject Claude MCP config/env when the loopback runtime is actually live
  • cover loopback config/env merging and MCP header scoping with regression tests

Testing

  • pnpm test src/gateway/tools-invoke-http.test.ts src/gateway/mcp-http.test.ts src/agents/cli-runner/bundle-mcp.test.ts
  • pnpm check (fails on unrelated preexisting main errors in extensions/matrix/src/doctor.ts, src/agents/openai-ws-stream.ts, and several cache/live test typings)
  • pnpm build (fails on unrelated preexisting main error in src/agents/openai-ws-stream.ts)

@cursor

cursor Bot commented Apr 4, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Adds a new in-process loopback MCP HTTP server and injects it into Claude CLI runs, which changes how tools can be invoked and relies on correct header/token scoping to avoid unintended access.

Overview
Restores background Claude CLI tool access by starting a token-authenticated loopback MCP HTTP server (/mcp) inside the gateway and injecting a strict MCP config + env vars into Claude CLI runs only when that loopback runtime is active.

Centralizes tool listing/invocation policy by introducing resolveGatewayScopedTools() and reusing it for both the new MCP server and the existing POST /tools/invoke endpoint, so session/account/channel scoping and gateway deny rules stay consistent.

Extends CLI bundle MCP preparation to support additionalConfig merging and to pass through extra env, and adds regression tests covering MCP runtime tracking/auth, header scoping, and config/env merging.

Reviewed by Cursor Bugbot for commit 6a71bdc. Bugbot is set up for automated code reviews on this repo. Configure here.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime agents Agent runtime and tooling labels Apr 4, 2026
@steipete steipete merged commit 3de09fb into main Apr 4, 2026
12 checks passed
@openclaw-barnacle openclaw-barnacle Bot added size: L maintainer Maintainer-authored PR labels Apr 4, 2026
@steipete steipete deleted the temp/landpr-35676 branch April 4, 2026 06:16
@steipete

steipete commented Apr 4, 2026

Copy link
Copy Markdown
Contributor Author

Landed via temp rebase onto main.

  • Gate: pnpm test src/gateway/tools-invoke-http.test.ts src/gateway/mcp-http.test.ts src/agents/cli-runner/bundle-mcp.test.ts
  • Repo-wide note: pnpm check and pnpm build still fail on unrelated preexisting main issues in extensions/matrix/src/doctor.ts, src/agents/openai-ws-stream.ts, and several cache/live test typings.
  • Land commit: 6a71bdc
  • Merge commit: 3de09fb

Thanks @mylukin!

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6a71bdc. Configure here.

Comment thread src/gateway/mcp-http.ts
delete merged.const;
mergedProps[key] = merged;
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Union schema flattening drops third+ variant const values

Low Severity

flattenUnionSchema fails to merge const values from 3+ variants of the same property key. After the first two const variants are merged, the result has an enum array (with const deleted). When a third variant arrives with a const value, neither the enum+enum branch (incoming lacks enum) nor the const+const branch (existing lacks const) matches, so the value is silently dropped with only a warning log. A missing branch for existing.enum + incoming.const means tools with richer anyOf/oneOf schemas lose valid input values in the MCP tool listing.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6a71bdc. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a71bdc65c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gateway/mcp-http.ts
Comment on lines +302 to +306
const next = resolveGatewayScopedTools({
cfg,
sessionKey,
messageProvider,
accountId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Apply owner-only tool filtering before exposing MCP tools

The loopback MCP handler resolves next.tools and exposes them directly, but it never applies applyOwnerOnlyToolPolicy the way /tools/invoke does. In deployments that intentionally allow owner-only tools in gateway.tools.allow (for example cron, gateway, nodes, or whatsapp_login), non-owner sessions using Claude CLI can still discover and invoke those tools through MCP, which is a privilege escalation relative to existing gateway behavior.

Useful? React with 👍 / 👎.

Comment thread src/gateway/mcp-http.ts
Comment on lines +122 to +123
const trimmed = rawSessionKey?.trim();
return !trimmed || trimmed === "main" ? resolveMainSessionKey(cfg) : trimmed;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor agent header when session key is absent

Session resolution currently defaults to the main session whenever x-session-key is empty, and this code path ignores the new x-openclaw-agent-id header that the CLI runner now injects. As a result, runs that specify agentId without sessionKey can resolve tools/policies for the main agent instead of the intended agent, leading to incorrect cross-agent tool scope.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an in-process MCP HTTP loopback bridge (src/gateway/mcp-http.ts) that lets background Claude CLI runs invoke OpenClaw tools over JSON-RPC, reusing the same resolveGatewayScopedTools path as /tools/invoke. The gateway starts the server at startup, and prepare.ts injects the loopback config and a per-run bearer token into the Claude CLI's env and MCP config only when the claude-cli backend is active. All remaining findings are P2.

Confidence Score: 5/5

Safe to merge — auth, loopback-only binding, and lifecycle cleanup are all correct; only minor P2 style observations remain.

The core security properties are sound: random 32-byte token, 127.0.0.1-only bind, token cleared on server close. Lifecycle integration in server.impl.ts properly closes the MCP server in both normal and emergency shutdown paths. Tests cover the key header-to-tool-resolution mapping, auth rejection, and config merging paths. Both remaining findings are non-blocking style improvements.

src/gateway/mcp-http.ts (x-openclaw-agent-id header and missing allowGatewaySubagentBinding/allowMediaInvokeCommands flags noted above)

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/mcp-http.ts
Line: 148-164

Comment:
**`x-openclaw-agent-id` header is configured but never consumed**

`createMcpLoopbackServerConfig` emits `"x-openclaw-agent-id": "${OPENCLAW_MCP_AGENT_ID}"` and `prepare.ts` populates `OPENCLAW_MCP_AGENT_ID` with `sessionAgentId`, but the MCP server's request handler never reads `x-openclaw-agent-id` (only `x-session-key`, `x-openclaw-account-id`, and `x-openclaw-message-channel` are read). Because `resolveGatewayScopedTools` already derives the agent ID from the `sessionKey`, the header is functionally redundant. Either remove it from the header map and the env injection to avoid confusion, or document that it's reserved for future use.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/gateway/mcp-http.ts
Line: 301-322

Comment:
**MCP tool resolution omits `allowGatewaySubagentBinding` and `allowMediaInvokeCommands`**

The direct `/tools/invoke` handler calls `resolveGatewayScopedTools` with `allowGatewaySubagentBinding: true` and `allowMediaInvokeCommands: true`, but the MCP loopback path doesn't pass either flag (both default to `undefined`/falsy). This creates a silent capability asymmetry: subagent-binding and media-invoke tools are reachable via HTTP but absent from the MCP tool catalog. If this is intentional scoping for the CLI bridge, a brief comment here would prevent future confusion about why the two surfaces diverge.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: restore claude cli loopback mcp bri..." | Re-trigger Greptile

Comment thread src/gateway/mcp-http.ts
Comment on lines +148 to +164
export function createMcpLoopbackServerConfig(port: number) {
return {
mcpServers: {
openclaw: {
type: "http",
url: `http://127.0.0.1:${port}/mcp`,
headers: {
Authorization: "Bearer ${OPENCLAW_MCP_TOKEN}",
"x-session-key": "${OPENCLAW_MCP_SESSION_KEY}",
"x-openclaw-agent-id": "${OPENCLAW_MCP_AGENT_ID}",
"x-openclaw-account-id": "${OPENCLAW_MCP_ACCOUNT_ID}",
"x-openclaw-message-channel": "${OPENCLAW_MCP_MESSAGE_CHANNEL}",
},
},
},
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 x-openclaw-agent-id header is configured but never consumed

createMcpLoopbackServerConfig emits "x-openclaw-agent-id": "${OPENCLAW_MCP_AGENT_ID}" and prepare.ts populates OPENCLAW_MCP_AGENT_ID with sessionAgentId, but the MCP server's request handler never reads x-openclaw-agent-id (only x-session-key, x-openclaw-account-id, and x-openclaw-message-channel are read). Because resolveGatewayScopedTools already derives the agent ID from the sessionKey, the header is functionally redundant. Either remove it from the header map and the env injection to avoid confusion, or document that it's reserved for future use.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/mcp-http.ts
Line: 148-164

Comment:
**`x-openclaw-agent-id` header is configured but never consumed**

`createMcpLoopbackServerConfig` emits `"x-openclaw-agent-id": "${OPENCLAW_MCP_AGENT_ID}"` and `prepare.ts` populates `OPENCLAW_MCP_AGENT_ID` with `sessionAgentId`, but the MCP server's request handler never reads `x-openclaw-agent-id` (only `x-session-key`, `x-openclaw-account-id`, and `x-openclaw-message-channel` are read). Because `resolveGatewayScopedTools` already derives the agent ID from the `sessionKey`, the header is functionally redundant. Either remove it from the header map and the env injection to avoid confusion, or document that it's reserved for future use.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/gateway/mcp-http.ts
Comment on lines +301 to +322
: (() => {
const next = resolveGatewayScopedTools({
cfg,
sessionKey,
messageProvider,
accountId,
excludeToolNames: NATIVE_TOOL_EXCLUDE,
});
const nextEntry = {
tools: next.tools,
toolSchema: buildToolSchema(next.tools),
configRef: cfg,
time: now,
};
toolCache.set(cacheKey, nextEntry);
for (const [key, entry] of toolCache) {
if (now - entry.time >= TOOL_CACHE_TTL_MS) {
toolCache.delete(key);
}
}
return nextEntry;
})();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 MCP tool resolution omits allowGatewaySubagentBinding and allowMediaInvokeCommands

The direct /tools/invoke handler calls resolveGatewayScopedTools with allowGatewaySubagentBinding: true and allowMediaInvokeCommands: true, but the MCP loopback path doesn't pass either flag (both default to undefined/falsy). This creates a silent capability asymmetry: subagent-binding and media-invoke tools are reachable via HTTP but absent from the MCP tool catalog. If this is intentional scoping for the CLI bridge, a brief comment here would prevent future confusion about why the two surfaces diverge.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/mcp-http.ts
Line: 301-322

Comment:
**MCP tool resolution omits `allowGatewaySubagentBinding` and `allowMediaInvokeCommands`**

The direct `/tools/invoke` handler calls `resolveGatewayScopedTools` with `allowGatewaySubagentBinding: true` and `allowMediaInvokeCommands: true`, but the MCP loopback path doesn't pass either flag (both default to `undefined`/falsy). This creates a silent capability asymmetry: subagent-binding and media-invoke tools are reachable via HTTP but absent from the MCP tool catalog. If this is intentional scoping for the CLI bridge, a brief comment here would prevent future confusion about why the two surfaces diverge.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling gateway Gateway runtime maintainer Maintainer-authored PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant