Skip to content

MCP runtimes duplicate stdio sessions for identical workspace/config #95506

Description

@bek91

Problem

Current MCP runtime ownership appears to be per session, even when many sessions use the same workspace and identical MCP config. That means multi-session workloads can duplicate MCP runtime state, catalog work, and stdio server processes/transports.

This is related to existing MCP performance/process-pressure reports, but it is a narrower source-level cardinality issue:

This issue is specifically about identical (workspaceDir, MCP config fingerprint) sessions fanning out into duplicate session-scoped MCP runtimes and duplicate stdio catalog work.

Validation Performed

I added a temporary/focused proof test locally against current code.

The test creates 8 sessions with:

  • the same workspaceDir
  • the same MCP config
  • one lightweight stdio MCP server
  • one tool returned from tools/list

For each session, the test calls getOrCreateSessionMcpRuntime(...). It then samples the MCP runtime census before catalog materialization.

Observed before catalog materialization:

runtimeCount = 8
workspaceConfigFingerprintCardinality = 1
connectedServerSessionCount = 0

Then the test calls runtime.getCatalog() for every runtime, which forces catalog loading and stdio MCP connection. It samples the census again and reads the stdio server log.

Observed after catalog materialization:

runtimeCount = 8
workspaceConfigFingerprintCardinality = 1
cachedCatalogCount = 8
catalogToolCount = 8
catalogServerCount = 8
connectedServerSessionCount = 8
transportCounts.stdio = 8
tools/list requests in server log = 8

This shows that identical workspace/config sessions currently fan out into one connected stdio MCP server session and one tools/list request per OpenClaw session.

Temporary Test Shape

The local proof test was added to src/agents/agent-bundle-mcp-runtime.test.ts and uses the existing test helper that writes a lightweight stdio MCP server.

Pseudo-shape:

const sessionCount = 8;
const cfg = {
  mcp: {
    servers: {
      fanoutProbe: {
        command: process.execPath,
        args: [serverPath],
        requestTimeoutMs: 3_000,
      },
    },
  },
};

const runtimes = await Promise.all(
  sessionIds.map((sessionId) =>
    getOrCreateSessionMcpRuntime({
      sessionId,
      sessionKey: `agent:test:${sessionId}`,
      workspaceDir: "/workspace",
      cfg,
    }),
  ),
);

expect(getSessionMcpRuntimeResourceCensus()).toMatchObject({
  runtimeCount: 8,
  workspaceConfigFingerprintCardinality: 1,
  connectedServerSessionCount: 0,
});

await Promise.all(runtimes.map((runtime) => runtime.getCatalog()));

const afterCatalog = getSessionMcpRuntimeResourceCensus();
expect(afterCatalog.runtimeCount).toBe(8);
expect(afterCatalog.workspaceConfigFingerprintCardinality).toBe(1);
expect(afterCatalog.cachedCatalogCount).toBe(8);
expect(afterCatalog.connectedServerSessionCount).toBe(8);
expect(afterCatalog.transportCounts.stdio).toBe(8);
expect(toolsListRequestsInServerLog).toBe(8);

Additional Measurement

I also ran a temporary local measurement through the actual diagnostics.stability handler using no-server MCP configs. This avoids spawning many child processes while checking runtime cardinality behavior.

For identical workspace/config sessions:

created-20:  runtimeCount=20,  workspaceConfigFingerprintCardinality=1
created-50:  runtimeCount=50,  workspaceConfigFingerprintCardinality=1
created-100: runtimeCount=100, workspaceConfigFingerprintCardinality=1

After catalog materialization in that no-server case:

catalog-materialized-20:  cachedCatalogCount=20
catalog-materialized-50:  cachedCatalogCount=50
catalog-materialized-100: cachedCatalogCount=100

Cleanup also behaved as expected:

after-dispose-all: runtimeCount=0
idle-sweep-before-wait: runtimeCount=20
after-idle-sweep: runtimeCount=0, swept=20

That measurement proves the runtime/catalog cardinality problem scales linearly with session count even when workspace/config cardinality is 1. The stdio proof test above proves the same pattern extends to actual connected MCP stdio sessions and duplicate tools/list calls.

Why This Matters

For many concurrent sessions in the same workspace with the same MCP config, OpenClaw can duplicate:

  • MCP runtime objects
  • cached catalogs
  • tools/list requests
  • stdio MCP server processes/transports
  • associated FD, memory, and CPU overhead

This looks like significant avoidable resource pressure in multi-session workloads.

Proposed Direction

Investigate sharing MCP runtime/catalog/transport ownership by (workspaceDir, configFingerprint) instead of by session alone, while keeping session-specific lifecycle state separate.

The fix needs to preserve:

  • per-session cleanup semantics
  • active lease protection
  • idle sweep behavior
  • config fingerprint invalidation
  • catalog in-flight sharing
  • safe disposal of stdio transports
  • diagnostics without raw session/workspace/config leakage

Acceptance Criteria

  • Identical workspace/config sessions do not start duplicate stdio MCP server sessions unnecessarily.
  • Duplicate tools/list calls collapse where sharing is valid.
  • Per-session leases and cleanup remain correct.
  • Idle sweep/dispose do not tear down shared MCP state while another session still owns/leases it.
  • Tests cover same workspace/config fan-out, catalog materialization, active leases, idle sweep, and disposal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.maintainerMaintainer-authored PR

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions