Skip to content

Agents: run bundle MCP tools in embedded Pi#48611

Merged
vincentkoc merged 5 commits intomainfrom
vincentkoc-code/bundle-mcp-tools-in-pi
Mar 17, 2026
Merged

Agents: run bundle MCP tools in embedded Pi#48611
vincentkoc merged 5 commits intomainfrom
vincentkoc-code/bundle-mcp-tools-in-pi

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

  • Problem: Claude-compatible bundle MCP config was imported into Pi settings, but embedded Pi did not actually expose or execute those MCP tools.
  • Why it matters: marketplace bundles like Context7 could install cleanly while still not working during embedded Pi agent turns.
  • What changed: add a bundle-MCP runtime bridge that launches enabled bundle MCP servers, lists their tools, exposes them as Pi custom tools, and wires that into both normal embedded Pi runs and compaction sessions.
  • What did NOT change (scope boundary): unsupported bundle capabilities such as Claude hook automation, agents, and LSP metadata are still detect-only.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • Enabled bundle mcpServers now expose runnable tools during embedded Pi agent turns.
  • Relative bundle MCP launches now default their working directory to the bundle root, which fixes common bundle layouts that reference local server scripts.
  • Bundle docs now describe actual embedded Pi MCP runtime behavior instead of only config import.

Security Impact (required)

  • New permissions/capabilities? (Yes/No) Yes
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) Yes
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:
    • Enabled bundle MCP servers can now run as subprocesses for embedded Pi tool calls.
    • Mitigation: only enabled bundles are considered, server paths still go through bundle boundary checks and path normalization, duplicate tool names are skipped instead of shadowing existing tools, and bundle runtime modules are still not loaded in-process.

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local workspace
  • Model/provider: mocked OpenAI responses in embedded Pi tests
  • Integration/channel (if any): Claude-style bundle MCP
  • Relevant config (redacted): plugins.entries.bundle-probe.enabled=true

Steps

  1. Create an enabled Claude-style bundle with .mcp.json pointing at a local stdio MCP server script.
  2. Run an embedded Pi turn that calls the bundle MCP tool.
  3. Confirm the tool result is returned and available in the follow-up assistant turn.

Expected

  • Bundle MCP tool is discoverable and runnable through embedded Pi.

Actual

  • After this change, the tool executes and the result appears in the session transcript and follow-up turn.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • pnpm test -- src/agents/pi-bundle-mcp-tools.test.ts src/agents/pi-embedded-runner.bundle-mcp.e2e.test.ts src/plugins/bundle-mcp.test.ts src/agents/pi-project-settings.bundle.test.ts src/agents/pi-project-settings.test.ts
    • pnpm build
    • pnpm docs:check-i18n-glossary
    • pnpm docs:check-links
    • direct local probe of createBundleMcpToolRuntime() against a real stdio MCP server script returning FROM-BUNDLE
  • Edge cases checked:
    • duplicate MCP tool names are skipped
    • relative bundle MCP server paths resolve against the bundle root
    • compaction path gets the same bundle MCP tool injection as normal runs
  • What you did not verify:
    • live third-party marketplace bundles against a real external model/provider on this branch

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly:
    • disable the bundle/plugin entry or revert commit 053580884d
  • Files/config to restore:
    • bundle/plugin enablement under plugins.entries
  • Known bad symptoms reviewers should watch for:
    • embedded Pi hangs or misses expected bundle MCP tools
    • duplicate tool-name warnings where a bundle tool collides with an existing tool name

Risks and Mitigations

  • Risk: bundle MCP subprocess launch may fail for malformed bundle config or server startup errors.
    • Mitigation: startup failures degrade to warnings and skip the broken server instead of breaking all tool loading.
  • Risk: embedded Pi run and compaction paths could drift again.
    • Mitigation: both call sites now share the same createBundleMcpToolRuntime() bridge, and both isolated + e2e tests cover the runtime path.

@vincentkoc vincentkoc self-assigned this Mar 17, 2026
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation agents Agent runtime and tooling size: L maintainer Maintainer-authored PR labels Mar 17, 2026
@vincentkoc
Copy link
Copy Markdown
Member Author

Follow-up fixes pushed after review:

  • fixed Claude inline mcpServers path resolution to use the plugin root instead of .claude-plugin/
  • added ${CLAUDE_PLUGIN_ROOT} expansion in bundle MCP normalization for command, args, cwd/workingDirectory, and string env values
  • narrowed docs/runtime wording to the actual supported scope: stdio bundle MCP servers
  • added regression coverage for inline manifest paths plus placeholder expansion in src/plugins/bundle-mcp.test.ts

Re-verified:

  • pnpm test -- src/plugins/bundle-mcp.test.ts src/agents/pi-bundle-mcp-tools.test.ts src/agents/pi-embedded-runner.bundle-mcp.e2e.test.ts src/agents/pi-project-settings.bundle.test.ts src/agents/pi-project-settings.test.ts
  • pnpm build
  • pnpm docs:check-i18n-glossary
  • pnpm docs:check-links

@vincentkoc
Copy link
Copy Markdown
Member Author

Second follow-up pushed after another review pass:

  • loader/diagnostics no longer silently overclaim bundle MCP support for URL-only or incomplete MCP configs
  • plugins info/loader diagnostics now warn when a bundle declares MCP servers that embedded Pi cannot run yet (stdio only today)
  • added loader regression coverage for a URL-only bundle MCP config

Re-verified:

  • pnpm test -- src/plugins/bundle-mcp.test.ts src/plugins/loader.test.ts src/agents/pi-bundle-mcp-tools.test.ts src/agents/pi-embedded-runner.bundle-mcp.e2e.test.ts src/agents/pi-project-settings.bundle.test.ts src/agents/pi-project-settings.test.ts
  • pnpm build
  • pnpm docs:check-i18n-glossary
  • pnpm docs:check-links

@openclaw-barnacle openclaw-barnacle Bot added the cli CLI command changes label Mar 17, 2026
@vincentkoc vincentkoc force-pushed the vincentkoc-code/bundle-mcp-tools-in-pi branch from 1c2cae4 to 751beb2 Compare March 17, 2026 04:45
@vincentkoc vincentkoc marked this pull request as ready for review March 17, 2026 04:45
@vincentkoc vincentkoc merged commit 06459ca into main Mar 17, 2026
14 of 15 checks passed
@vincentkoc vincentkoc deleted the vincentkoc-code/bundle-mcp-tools-in-pi branch March 17, 2026 04:46
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 17, 2026

Greptile Summary

This PR implements the missing runtime half of bundle MCP support: enabled bundle mcpServers are now discovered, launched as stdio subprocesses, and wired as custom tools into both the normal embedded Pi run path (run/attempt.ts) and the compaction path (compact.ts). It also adds a /mcp chat command and openclaw mcp CLI for managing mcp.servers config, expands ${CLAUDE_PLUGIN_ROOT} placeholders, and defaults the working directory of relative bundle MCP launches to the bundle root.

Key findings from this review:

  • P1 – No startup timeout on bundle MCP server connect/listTools (src/agents/pi-bundle-mcp-tools.ts lines 171–173): client.connect(transport) and listAllTools(client) are awaited without any deadline. A bundle MCP server that stalls during startup will block createBundleMcpToolRuntime indefinitely, hanging every embedded Pi run and compaction attempt that calls it. The catch block only fires on rejection, not on a stall.

  • P1 – No timeout on callTool during tool execution (src/agents/pi-bundle-mcp-tools.ts lines 194–204): The tool's execute closure awaits client.callTool(...) without a timeout. A server that becomes unresponsive mid-run will suspend the active agent turn indefinitely.

  • P2 – attachStderrLogging called before the transport process is spawned (src/agents/pi-bundle-mcp-tools.ts lines 164–169): transport.stderr is only populated after client.connect() starts the subprocess. Calling attachStderrLogging before connect() means the guard if (!stderr || ...) always fires and the function returns undefined for every server, silently disabling all MCP server stderr logging. The fix is to move the call to after client.connect(transport).

  • P2 – toStringArray silently drops non-string args (src/agents/mcp-stdio.ts lines 34–40): Unlike toStringRecord (which coerces numbers and booleans to strings for env vars), toStringArray silently filters out non-string array elements. A config with args: ["--port", 8080] would silently launch the server as ["--port"] only.

The disposal patterns in both compact.ts and run/attempt.ts are correct — bundleMcpRuntime?.dispose() is placed inside a finally block in both files. The access-control model for the new /mcp command correctly mirrors the existing /config command pattern.

Confidence Score: 2/5

  • Two P1 issues mean a misbehaving bundle MCP server can hang Pi runs and tool calls indefinitely; do not merge until connection and call-execution timeouts are added.
  • The architectural approach is sound and the disposal/cleanup patterns are correct, but two missing timeouts (server connect/listTools and per-call callTool) represent genuine hang vectors that could make the embedded Pi agent unresponsive in production when any bundle MCP server stalls. There is also a silent stderr-logging bug. These issues should be addressed before merging.
  • src/agents/pi-bundle-mcp-tools.ts requires the most attention: it needs connection/listTools timeouts, a per-call timeout, and the stderr-logging call moved post-connect. src/agents/mcp-stdio.ts needs the toStringArray coercion fix.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-bundle-mcp-tools.ts
Line: 171-173

Comment:
**No timeout on server connect / tool listing**

`client.connect(transport)` and `listAllTools(client)` are awaited without any timeout. The `@modelcontextprotocol/sdk` `StdioClientTransport` does not impose a startup or capability-negotiation deadline on its own. If a bundle MCP server process hangs at startup (e.g., waits for stdin, never writes its capabilities message, or enters an infinite loop), `createBundleMcpToolRuntime` will never resolve, blocking every embedded Pi run and compaction that calls it.

The `catch` block on line 207 only fires if the promise *rejects*, not if it *stalls*. A stalling process will keep the Pi agent suspended indefinitely.

Consider wrapping both calls with a `Promise.race` against a short deadline (e.g., 10 s) and treating the timeout as a startup failure that triggers the existing `logWarn + disposeSession` path:

```ts
const CONNECT_TIMEOUT_MS = 10_000;

async function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
  let timer: ReturnType<typeof setTimeout>;
  const timeout = new Promise<never>((_, reject) => {
    timer = setTimeout(() => reject(new Error(`timed out after ${ms}ms: ${label}`)), ms);
  });
  try {
    return await Promise.race([promise, timeout]);
  } finally {
    clearTimeout(timer);
  }
}

// in the server loop:
await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `connect ${serverName}`);
const listedTools = await withTimeout(listAllTools(client), CONNECT_TIMEOUT_MS, `listTools ${serverName}`);
```

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/agents/pi-bundle-mcp-tools.ts
Line: 194-204

Comment:
**No timeout on tool call execution**

`client.callTool(...)` is awaited with no deadline. If a bundle MCP server becomes unresponsive after startup (e.g., the subprocess hangs mid-execution), the Pi agent will stall indefinitely waiting for the tool result. This is separate from the startup timeout on `connect`/`listTools`.

The MCP SDK does not add a per-call timeout by default. A timeout here is especially important because tool calls happen inside an active agent turn, and a hung tool call will suspend the entire conversation loop.

Consider applying the same `withTimeout` helper described in the `connect`/`listTools` comment above, using a longer deadline appropriate for tool execution (e.g., the same timeout that guards the overall Pi attempt, or a configurable per-tool limit).

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/agents/pi-bundle-mcp-tools.ts
Line: 164-169

Comment:
**`attachStderrLogging` called before the transport process is spawned**

`attachStderrLogging` reads `transport.stderr` at construction time (line 168), but the `StdioClientTransport` only spawns the child process — and therefore only populates `transport.stderr` — inside its `start()` method, which is invoked by `client.connect(transport)` on line 172.

At line 168 `transport.stderr` is `undefined`, so `attachStderrLogging` hits the early-return guard (`if (!stderr || ...)`) and returns `undefined` for every server. As a result `session.detachStderr` is always `undefined` and no stderr output from bundle MCP servers is ever logged.

Move the `attachStderrLogging` call to after `client.connect(transport)` and assign the result back to `session.detachStderr`:

```ts
await client.connect(transport);
session.detachStderr = attachStderrLogging(serverName, transport);
const listedTools = await listAllTools(client);
```

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/agents/mcp-stdio.ts
Line: 34-40

Comment:
**Non-string args silently dropped; inconsistent with `toStringRecord`**

`toStringArray` filters out any non-`string` element — including numbers — without a warning. For example an MCP config with `args: ["--port", 8080]` would silently become `["--port"]`, causing the server to launch without the port argument.

By contrast, `toStringRecord` (which normalises the `env` map) explicitly converts numbers and booleans to strings:

```ts
if (typeof entry === "number" || typeof entry === "boolean") {
  return [key, String(entry)] as const;
}
```

Either apply the same coercion to `toStringArray`, or emit a warning when non-string elements are encountered so the misconfiguration is visible in logs:

```ts
function toStringArray(value: unknown): string[] | undefined {
  if (!Array.isArray(value)) return undefined;
  const entries = value.map((entry) => {
    if (typeof entry === "string") return entry;
    if (typeof entry === "number" || typeof entry === "boolean") return String(entry);
    return null; // unrepresentable — will be dropped with a log below
  });
  const result = entries.filter((e): e is string => e !== null);
  return result;
}
```

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

Last reviewed commit: 751beb2

Comment on lines +171 to +173
try {
await client.connect(transport);
const listedTools = await listAllTools(client);
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.

P1 No timeout on server connect / tool listing

client.connect(transport) and listAllTools(client) are awaited without any timeout. The @modelcontextprotocol/sdk StdioClientTransport does not impose a startup or capability-negotiation deadline on its own. If a bundle MCP server process hangs at startup (e.g., waits for stdin, never writes its capabilities message, or enters an infinite loop), createBundleMcpToolRuntime will never resolve, blocking every embedded Pi run and compaction that calls it.

The catch block on line 207 only fires if the promise rejects, not if it stalls. A stalling process will keep the Pi agent suspended indefinitely.

Consider wrapping both calls with a Promise.race against a short deadline (e.g., 10 s) and treating the timeout as a startup failure that triggers the existing logWarn + disposeSession path:

const CONNECT_TIMEOUT_MS = 10_000;

async function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
  let timer: ReturnType<typeof setTimeout>;
  const timeout = new Promise<never>((_, reject) => {
    timer = setTimeout(() => reject(new Error(`timed out after ${ms}ms: ${label}`)), ms);
  });
  try {
    return await Promise.race([promise, timeout]);
  } finally {
    clearTimeout(timer);
  }
}

// in the server loop:
await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `connect ${serverName}`);
const listedTools = await withTimeout(listAllTools(client), CONNECT_TIMEOUT_MS, `listTools ${serverName}`);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-bundle-mcp-tools.ts
Line: 171-173

Comment:
**No timeout on server connect / tool listing**

`client.connect(transport)` and `listAllTools(client)` are awaited without any timeout. The `@modelcontextprotocol/sdk` `StdioClientTransport` does not impose a startup or capability-negotiation deadline on its own. If a bundle MCP server process hangs at startup (e.g., waits for stdin, never writes its capabilities message, or enters an infinite loop), `createBundleMcpToolRuntime` will never resolve, blocking every embedded Pi run and compaction that calls it.

The `catch` block on line 207 only fires if the promise *rejects*, not if it *stalls*. A stalling process will keep the Pi agent suspended indefinitely.

Consider wrapping both calls with a `Promise.race` against a short deadline (e.g., 10 s) and treating the timeout as a startup failure that triggers the existing `logWarn + disposeSession` path:

```ts
const CONNECT_TIMEOUT_MS = 10_000;

async function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
  let timer: ReturnType<typeof setTimeout>;
  const timeout = new Promise<never>((_, reject) => {
    timer = setTimeout(() => reject(new Error(`timed out after ${ms}ms: ${label}`)), ms);
  });
  try {
    return await Promise.race([promise, timeout]);
  } finally {
    clearTimeout(timer);
  }
}

// in the server loop:
await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `connect ${serverName}`);
const listedTools = await withTimeout(listAllTools(client), CONNECT_TIMEOUT_MS, `listTools ${serverName}`);
```

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

Comment on lines +194 to +204
execute: async (_toolCallId, input) => {
const result = (await client.callTool({
name: tool.name,
arguments: isRecord(input) ? input : {},
})) as CallToolResult;
return toAgentToolResult({
serverName,
toolName: tool.name,
result,
});
},
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.

P1 No timeout on tool call execution

client.callTool(...) is awaited with no deadline. If a bundle MCP server becomes unresponsive after startup (e.g., the subprocess hangs mid-execution), the Pi agent will stall indefinitely waiting for the tool result. This is separate from the startup timeout on connect/listTools.

The MCP SDK does not add a per-call timeout by default. A timeout here is especially important because tool calls happen inside an active agent turn, and a hung tool call will suspend the entire conversation loop.

Consider applying the same withTimeout helper described in the connect/listTools comment above, using a longer deadline appropriate for tool execution (e.g., the same timeout that guards the overall Pi attempt, or a configurable per-tool limit).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-bundle-mcp-tools.ts
Line: 194-204

Comment:
**No timeout on tool call execution**

`client.callTool(...)` is awaited with no deadline. If a bundle MCP server becomes unresponsive after startup (e.g., the subprocess hangs mid-execution), the Pi agent will stall indefinitely waiting for the tool result. This is separate from the startup timeout on `connect`/`listTools`.

The MCP SDK does not add a per-call timeout by default. A timeout here is especially important because tool calls happen inside an active agent turn, and a hung tool call will suspend the entire conversation loop.

Consider applying the same `withTimeout` helper described in the `connect`/`listTools` comment above, using a longer deadline appropriate for tool execution (e.g., the same timeout that guards the overall Pi attempt, or a configurable per-tool limit).

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

Comment on lines +164 to +169
const session: BundleMcpSession = {
serverName,
client,
transport,
detachStderr: attachStderrLogging(serverName, transport),
};
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 attachStderrLogging called before the transport process is spawned

attachStderrLogging reads transport.stderr at construction time (line 168), but the StdioClientTransport only spawns the child process — and therefore only populates transport.stderr — inside its start() method, which is invoked by client.connect(transport) on line 172.

At line 168 transport.stderr is undefined, so attachStderrLogging hits the early-return guard (if (!stderr || ...)) and returns undefined for every server. As a result session.detachStderr is always undefined and no stderr output from bundle MCP servers is ever logged.

Move the attachStderrLogging call to after client.connect(transport) and assign the result back to session.detachStderr:

await client.connect(transport);
session.detachStderr = attachStderrLogging(serverName, transport);
const listedTools = await listAllTools(client);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-bundle-mcp-tools.ts
Line: 164-169

Comment:
**`attachStderrLogging` called before the transport process is spawned**

`attachStderrLogging` reads `transport.stderr` at construction time (line 168), but the `StdioClientTransport` only spawns the child process — and therefore only populates `transport.stderr` — inside its `start()` method, which is invoked by `client.connect(transport)` on line 172.

At line 168 `transport.stderr` is `undefined`, so `attachStderrLogging` hits the early-return guard (`if (!stderr || ...)`) and returns `undefined` for every server. As a result `session.detachStderr` is always `undefined` and no stderr output from bundle MCP servers is ever logged.

Move the `attachStderrLogging` call to after `client.connect(transport)` and assign the result back to `session.detachStderr`:

```ts
await client.connect(transport);
session.detachStderr = attachStderrLogging(serverName, transport);
const listedTools = await listAllTools(client);
```

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

Comment thread src/agents/mcp-stdio.ts
Comment on lines +34 to +40
function toStringArray(value: unknown): string[] | undefined {
if (!Array.isArray(value)) {
return undefined;
}
const entries = value.filter((entry): entry is string => typeof entry === "string");
return entries.length > 0 ? entries : [];
}
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 Non-string args silently dropped; inconsistent with toStringRecord

toStringArray filters out any non-string element — including numbers — without a warning. For example an MCP config with args: ["--port", 8080] would silently become ["--port"], causing the server to launch without the port argument.

By contrast, toStringRecord (which normalises the env map) explicitly converts numbers and booleans to strings:

if (typeof entry === "number" || typeof entry === "boolean") {
  return [key, String(entry)] as const;
}

Either apply the same coercion to toStringArray, or emit a warning when non-string elements are encountered so the misconfiguration is visible in logs:

function toStringArray(value: unknown): string[] | undefined {
  if (!Array.isArray(value)) return undefined;
  const entries = value.map((entry) => {
    if (typeof entry === "string") return entry;
    if (typeof entry === "number" || typeof entry === "boolean") return String(entry);
    return null; // unrepresentable — will be dropped with a log below
  });
  const result = entries.filter((e): e is string => e !== null);
  return result;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/mcp-stdio.ts
Line: 34-40

Comment:
**Non-string args silently dropped; inconsistent with `toStringRecord`**

`toStringArray` filters out any non-`string` element — including numbers — without a warning. For example an MCP config with `args: ["--port", 8080]` would silently become `["--port"]`, causing the server to launch without the port argument.

By contrast, `toStringRecord` (which normalises the `env` map) explicitly converts numbers and booleans to strings:

```ts
if (typeof entry === "number" || typeof entry === "boolean") {
  return [key, String(entry)] as const;
}
```

Either apply the same coercion to `toStringArray`, or emit a warning when non-string elements are encountered so the misconfiguration is visible in logs:

```ts
function toStringArray(value: unknown): string[] | undefined {
  if (!Array.isArray(value)) return undefined;
  const entries = value.map((entry) => {
    if (typeof entry === "string") return entry;
    if (typeof entry === "number" || typeof entry === "boolean") return String(entry);
    return null; // unrepresentable — will be dropped with a log below
  });
  const result = entries.filter((e): e is string => e !== null);
  return result;
}
```

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

Copy link
Copy Markdown

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

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: 751beb26ab

ℹ️ 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/plugins/loader.ts
Comment on lines +1125 to +1129
const runtimeSupport = inspectBundleMcpRuntimeSupport({
pluginId: record.id,
rootDir: record.rootDir,
bundleFormat: record.bundleFormat,
});
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 Guard bundle MCP runtime inspection from parse failures

This inspection runs during plugin loading for every enabled bundle that advertises mcpServers, but inspectBundleMcpRuntimeSupport() reads/parses bundle MCP config and can throw on malformed JSON (for example a broken .mcp.json), which aborts loadOpenClawPlugins() instead of emitting a warning. In that scenario, a single bad bundle MCP file can take down plugin loading/startup rather than being skipped as a diagnostic, so this call site should catch inspection errors and downgrade them to plugin diagnostics.

Useful? React with 👍 / 👎.

Comment on lines +1551 to +1554
const bundleMcpRuntime = toolsEnabled
? await createBundleMcpToolRuntime({
workspaceDir: effectiveWorkspace,
cfg: params.config,
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 Dispose bundle MCP runtime when lock acquisition fails

The bundle MCP runtime is created before acquireSessionWriteLock, but cleanup is only performed in the inner finally that executes after the lock is acquired; if lock acquisition times out or throws (contention/stale-lock scenarios), these spawned MCP subprocesses are never disposed. That leaks child processes/resources on failed attempts, so disposal needs to be guaranteed in an outer finally or runtime creation should be moved after lock acquisition.

Useful? React with 👍 / 👎.

Comment on lines +592 to +595
const bundleMcpRuntime = toolsEnabled
? await createBundleMcpToolRuntime({
workspaceDir: effectiveWorkspace,
cfg: params.config,
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 Close compaction MCP runtime on session-lock errors

Compaction has the same lifecycle gap: the MCP runtime is started before acquireSessionWriteLock, while bundleMcpRuntime.dispose() only runs in the inner finally after lock acquisition. When lock acquisition fails (e.g., timeout under contention), the runtime is left running and leaks subprocesses, so cleanup must also be guaranteed for pre-lock failure paths.

Useful? React with 👍 / 👎.

nikolaisid pushed a commit to nikolaisid/openclaw that referenced this pull request Mar 18, 2026
* Agents: run bundle MCP tools in embedded Pi

* Plugins: fix bundle MCP path resolution

* Plugins: warn on unsupported bundle MCP transports

* Commands: add embedded Pi MCP management

* Config: move MCP management to top-level config
sbezludny pushed a commit to sbezludny/openclaw that referenced this pull request Mar 27, 2026
* Agents: run bundle MCP tools in embedded Pi

* Plugins: fix bundle MCP path resolution

* Plugins: warn on unsupported bundle MCP transports

* Commands: add embedded Pi MCP management

* Config: move MCP management to top-level config
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
* Agents: run bundle MCP tools in embedded Pi

* Plugins: fix bundle MCP path resolution

* Plugins: warn on unsupported bundle MCP transports

* Commands: add embedded Pi MCP management

* Config: move MCP management to top-level config
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* Agents: run bundle MCP tools in embedded Pi

* Plugins: fix bundle MCP path resolution

* Plugins: warn on unsupported bundle MCP transports

* Commands: add embedded Pi MCP management

* Config: move MCP management to top-level config
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* Agents: run bundle MCP tools in embedded Pi

* Plugins: fix bundle MCP path resolution

* Plugins: warn on unsupported bundle MCP transports

* Commands: add embedded Pi MCP management

* Config: move MCP management to top-level config
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling cli CLI command changes docs Improvements or additions to documentation maintainer Maintainer-authored PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant