Saltar al contenido principal
L

dsh-cc-computer-use-mcp

liufeicc/dsh-cc-computer-use-mcp

Wires the local cc-computer-use MCP server into DSH with one process per root session, giving each session its own Xephyr sandbox screen and private AT-SPI bus (requires the cc-computer-use MCP server).

Instalar

dsh plugin --profile web add github:liufeicc/dsh-cc-computer-use-mcp

README

English | 中文

dsh-cc-computer-use-mcp

Wires a local cc-computer-use MCP server into DeepSeek Harness — one independent process per root session, so every session gets its own Xephyr sandbox screen and its own private AT-SPI bus.

What it adds

One Host plugin row in profile web:

id: cc-cu-per-agent
name: '@local/dsh-cc-computer-use-mcp'

It listens for agent/created and mounts one @deepseek-ai/dsh-mcp-client on each root agent's own agent.ctx. The model tool table then gains 17 tools named mcp__cc-computer-use__<rawName>:

ToolPurpose
get_ui_treeRead the accessibility tree (structured text; the primary perception channel)
get_screen_textOCR text with coordinates (the perception channel for grey-area apps)
find_element / element_infoFind elements by text/role/app
click / double_click / type_text / press_key / scroll / dragElement-first three-tier actions
get_last_click_imageLook back at where the last coordinate click actually landed
act_sequenceSubmit a batch of actions in one call
get_screen_layout / screenshotScreen layout / screenshot
list_windows / wait_window / launch_appEnumerate windows / wait for one / launch an app on the target display

Mounting follows the workspace MCP switch (since 2026-10-23)

These tools register in each agent's own layer (scope-local): the global lens cannot see them, so @local/dsh-mcp-workspace-policy's MCP menu used to omit this server entirely and restrict could not hide it. The two sides are now wired together:

  • this plugin calls declarePerAgent({ name }) on the policy service, so the menu lists cc-computer-use with the same tool-count hint as any other server (the Host counts from the live sessions that mounted it);
  • unchecked means not mounted — no MCP process, no Xephyr, no AT-SPI bus (default off, matching global MCP servers);
  • check/uncheck makes the policy emit mcp-workspace/changed; this plugin mounts or calls fiber.dispose() for the live sessions in that workspace, so the child exits and atexit reclaims the screen;
  • when the policy service is absent (not installed, or an older version without the declaration API) it fails open and mounts as before — a missing policy must never take the user's tools away.

⚠️ After upgrading, check cc-computer-use in the MCP menu for every workspace that needs computer control. Default-off means unselected workspaces no longer get these tools.

On plugin hot-reload the old fiber is stored on agent[Symbol.for('dsh-cc-computer-use-mcp/mounted-fiber')]; the new instance adopts it and then keeps or disposes it according to the latest selection — otherwise a second process would be mounted and hit mcp-client's serverName reservation error.

Why one process per session

The problem

A cc-computer-use sandbox is one MCP server process = one Xephyr + one private AT-SPI bus. It was designed around Claude Code's model (one MCP server child per session), but DSH's @deepseek-ai/dsh-mcp-client is mounted at profile scope (scopeOf(ctx) ?? ctx.root), so the whole dsh process spawns exactly one server. Every session therefore shared:

  • the same Xephyr (one X11 pointer/keyboard focus),
  • the same i3 window tree — a window A launched with launch_app shows up in B's list_windows, and B's click can land on A's window,
  • the same RefTable (ref numbers collide) and the same ScreenLock (one session holds it, the other is refused),
  • the same private AT-SPI bus.

Two agents using it at once fight over the sandbox — the "they're stealing my sandbox" symptom.

Why not several sandboxes in one process

That would use fewer processes, but it hits a hard libatspi constraint: atspi_init() is process-wide and one-shot, so a process can bind exactly one AT-SPI bus, and exit + re-init against another bus is known to corrupt its internal state (see _BUS_STALE_ERROR in cc-computer-use's backend/linux/atspi.py). Two screens imply two buses, so two screens require two processes. This is also cc-computer-use's own design assumption.

Why only root agents

A subagent is the same task's other hand and should work on the same screen (serialization is provided by cc-computer-use's screen lock). DSH's tool view inherits along the scope ancestor chain (ToolRuntime.view → chainLayers: the global layer plus every ancestor layer), so a subagent already sees the tools mounted by its parent and must not start another process — that would leave one sandbox orphaned.

Three predicates identify a subagent; any one being true skips the mount: agent.parentAgent, session.header.origin === 'subagent', session.header.delegationDepth > 0.

Lifecycle

agent.ctx is agent-local: "its contributions are agent-local, unwind on disposal". Closing a session unwinds the plugin, which disconnects the MCP child, which exits and lets cc-computer-use's atexit reclaim its Xephyr. No orphan screens.

Why the same serverName is safe

Tools register through ctx.tools.register as scope-local. DSH's own duplicate-name error spells out the contract:

tool "X" is already registered (for a per-agent variant, register through that agent's agent.ctx instead)

The same reasoning holds for systemPrompt.section (a scoped section shadows a global one) and mcpResources.register (layered by the calling scope), so many agents can share cc-computer-use without colliding, and the model-facing tool names stay stable.

Installation / new-environment notes

  1. The cc-computer-use frozen artifact must exist at config.command (<cc-computer-use repo>/dist/computer-use-mcp). Re-run its build.sh after changing its source.

  2. This package needs @deepseek-ai/dsh-mcp-client to be resolvable. Its package.json does not declare it (profiles have no @deepseek-ai/*), so a symlink provides it:

    mkdir -p node_modules/@deepseek-ai
    ln -sfn <dsh install dir>/node_modules/@deepseek-ai/dsh-mcp-client \
            node_modules/@deepseek-ai/dsh-mcp-client
    

    node_modules/ is git-ignored; recreate the link on a new machine and after a DSH upgrade that moves the install directory.

  3. Profile web links @local/dsh-cc-computer-use-mcp here, like the other local bundles.

Verifying two sessions get two screens

  1. Call mcp__cc-computer-use__get_ui_tree once in each session (the sandbox starts lazily on first use).

  2. Inspect processes and display numbers:

    ps -ef | grep -E "computer-use-mcp|Xephyr" | grep -v grep
    ls -d /tmp/cc-cu-at-spi-d* 2>/dev/null
    

    Expect two computer-use-mcp processes, two Xephyrs on different display numbers, and two distinct /tmp/cc-cu-at-spi-d<num>-* directories.

  3. Launch an app with launch_app in session A and run list_windows in session B — B must not see A's window.

Offline test

node test/gating.mjs

No processes and no host required: a fake context exercises the gating logic (mount only when checked, dispose on uncheck, skip subagents, fail open without the policy service, adopt the old fiber on hot-reload).

License

MIT — see LICENSE.

Plugins relacionados