DSH → Codex Delegate MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@DSH → Codex Delegate MCPDelegate to Codex: inspect this workspace read-only and summarize its purpose"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
DSH → Codex Delegate MCP
A governed stdio MCP server that lets DeepSeek Harness (dsh) delegate work to Codex on the same machine. As of 0.2.0 the engine is Codex's own codex mcp-server (tools codex / codex-reply), and this file is a thin governance proxy in front of it.
Why not mount codex mcp-server directly: on that server the model chooses sandbox (the enum includes danger-full-access), cwd (any path on the machine), config (a free-form object that overrides any Codex setting, including MCP servers) and base-instructions. Mounting it as-is hands a privilege knob to the delegated agent — or to anything it reads. The proxy takes only prompt, workspace, mode and thread, pins approval-policy: never, and never forwards config/base-instructions/model.
It does not expose the current Codex desktop conversation. It uses the Codex account and configuration already available to the local CLI.
Safety defaults
The default tool mode is
read-only.The server only accepts the directory where
dshwas started, or a descendant of it.workspace-writeis disabled unless the DSH patch explicitly setsCODEX_DELEGATE_ALLOW_WRITE: 'true'.danger-full-accessis never available.The server uses stdio; it does not open a network port.
Related MCP server: deepseek-subagent-mcp
Get it
The public repository is dsh-codex-delegate-mcp; clone it and the folder name
becomes the repo name, not the dsh-codex-delegate-mcp shorthand used in the
examples below. Clone it anywhere, then adjust two machine-local facts in
dsh-codex-delegate.cordis.patch.yml: the args path to server.mjs, and
CODEX_BINARY, which must point at a real codex executable (an .exe; on
Windows a .cmd shim fails a shell-less stdio spawn). CODEX_DELEGATE_ROOT and
CODEX_DELEGATE_WRITE_ROOT are pinned to one root for the bridge's whole
lifetime — widen or move them to change the scope.
Enable in DSH
The folder location and the interpreter path are baked into the patch, so keep this folder where it is on
this machine: D:\DeepSeek\dsh-codex-delegate-mcp (see "Get it" above for a fresh clone elsewhere).
For a terminal / CLI run:
dsh web --patch .\dsh-codex-delegate-mcp\dsh-codex-delegate.cordis.patch.ymlFor a terminal/headless run:
dsh --profile headless --patch .\dsh-codex-delegate-mcp\dsh-codex-delegate.cordis.patch.yml "Use mcp__codex__delegate_to_codex to inspect this workspace and summarize its purpose."For DSH Desktop (the app builds its own argv, so --patch is not reachable): put the same - insert: block
into the profile's user patch layer %DSH_HOME%\profiles\web\cordis.patch.yml. That layer is watched and
re-applied on save — no restart needed (it re-applies on a config diff, not on mtime; see "Reloading" below).
That is the route used on this machine. Validate the composition without booting anything:
dsh web --dump-config --patch .\dsh-codex-delegate-mcp\dsh-codex-delegate.cordis.patch.ymlDSH will discover the tool as mcp__codex__delegate_to_codex.
In DSH, make the delegation explicit, for example: 请委派给 Codex:检查当前项目的测试失败原因,只读分析。 The bridge does not take over this Codex desktop conversation; it starts a fresh local Codex CLI task.
Machine-local corrections verified on 2026-09-04 (codex-cli 0.148.0-alpha.9)
Historical: these three bugs belonged to the
codex execengine and are gone in 0.2.0, where Codex's own MCP server is the engine. They are kept because they explain why the wrapper exists at all — and item 4 (a real executable forcommand, plusCODEX_BINARY) still applies.
The original package could complete an MCP handshake but never a real delegation. Three defects, all fixed in server.mjs:
--sandbox <mode>and--approve-for-meare mutually exclusive, so every call died withexit=2before Codex started. Nowread-onlypins--sandbox read-onlyandworkspace-writeuses--approve-for-me.spawn()inherited an open stdin pipe.codex execappends stdin as a<stdin>block and waits for EOF, so the call hung until the timeout killed it. Nowstdio: ['ignore', 'pipe', 'pipe'].A non-zero exit or timeout was reported as a successful tool result. Now it is returned with
isError: true.
Two configuration corrections in the patch (not in server.mjs): command must be a real executable — on this machine node on PATH resolves to a .cmd shim, and a stdio spawn without a shell fails on it — and CODEX_BINARY must point at C:\Users\Administrator\.codex\plugins\.plugin-appserver\codex.exe, because codex is not on PATH here.
CODEX_DELEGATE_ROOT is pinned to D:\DeepSeek: the bridge accepts one root for its whole lifetime, so it cannot follow whichever workspace a session opens. Change it there to widen or move the scope.
Allow edits
Write mode is fenced, not global. Two envs decide it:
env | effect |
| master switch; |
| optional narrower root. When set, a write call whose |
So "open the write gate" need not mean "free to write anywhere under the read root". The live config on this machine runs ALLOW_WRITE: 'true' with WRITE_ROOT: <workspace>\DeepSeek子代理: Codex may write into the deliverables folder and nowhere else. To withdraw write capability, set ALLOW_WRITE: 'false'. Bump CODEX_BRIDGE_REV after either change, or the running child keeps the old environment.
One nuance to read correctly in a result: a fresh write run uses --approve-for-me and reads back Approval: on-request; a resumed write run must use -c approval_policy="never" (because exec resume accepts no approval flags) and reads back Approval: never. Only the second is fully unprompted.
Activity: what Codex did, not just what it answered
codex exec --json would cost the policy header, so the bridge keeps the plain invocation and reads Codex's own rollout record ($CODEX_HOME/sessions/**/rollout-*<session-id>.jsonl) for the turn that just ran. Every result then carries:
Steps: 10
Codex activity:
$ exec: const r = await tools.shell_command({command: "Get-ChildItem …"…
→ result: Script completed Wall time 0.3 seconds Output: Exit code: 0 …The last 14 steps (each truncated to 240 chars) are returned; pass include_activity: false to drop the block. This is also where the safety read-back comes from: turn_context records sandbox_policy.type, approval_policy, cwd, and workspace_roots per turn, so the bridge compares the recorded policy against the requested one and fails the call on a mismatch or on any root outside the allowed root — instead of trusting what it asked for.
One practical side effect of watching the activity: Codex's shell child runs PowerShell in ConstrainedLanguage mode, so [PSCustomObject]@{…} and .NET static calls fail there. Phrase delegated prompts with cmdlets and core types, or expect a wasted round trip.
Multi-step delegation
Each result opens with a read-back block:
Codex completed the delegated task.
Session: 01a07fef-4494-76d3-b674-9ae5821898ec
Workdir: D:\DeepSeek
Sandbox: workspace-write [workdir, /tmp, $TMPDIR]
Approval: never
Model: gpt-5.6-luna
Tokens: 17956
Next step: pass resume_session: "01a07fef-..." to continue this exact conversation.Pass that id back as resume_session to continue the same Codex conversation instead of starting a fresh one — the follow-up sees what Codex already read and wrote. Sessions live in $CODEX_HOME/sessions/rollout-*<id>.jsonl, so a resume survives a bridge restart; deleting those files does not.
codex exec resume accepts neither --sandbox, --approve-for-me, nor --cd, so a resumed run pins its policy through -c sandbox_mode=... (plus -c approval_policy="never" for writes), and the bridge then verifies the policy from codex's own header: a mismatch — or a workdir outside the allowed root — fails the call instead of reporting a success.
Reloading after editing server.mjs
The harness spawns the bridge once, at mount. Editing server.mjs alone changes nothing, and touching only its mtime changes nothing either: the layer re-applies on a config diff. Bump CODEX_BRIDGE_REV to force the stdio child to respawn and pick up new code.
Verified on 2026-09-04, through DSH's own MCP client (codex-cli 0.148.0-alpha.9)
Case | Result |
handshake + | pass |
| refused before spawning |
| refused before spawning |
read-only delegation + policy read-back | pass, |
| pass — recalled a value from the earlier turn |
write delegation | pass — file created with the requested bytes, checked on disk |
write + resume | pass — same session id, |
codex argument error / timeout | reported as |
Cost note: one delegated run persists a ~60–80 KB rollout-*.jsonl, and --ephemeral is deliberately not used because it would break resume_session. Delete old rollouts to reclaim space.
Requirements
Node.js 20 or newer
DeepSeek Harness CLI (
dsh) with@deepseek-ai/dsh-mcp-clientavailableCodex CLI authenticated on the same machine
No npm package installation is required for this MCP server itself.
0.2.0 — engine semantics (measured 2026-09-08)
Threads live in the engine process. Codex's mcp-server keeps a conversation in memory, so the proxy holds one persistent engine instead of spawning per call. A per-call process made every codex-reply fail with Session not found — that was the first thing this version had to fix. Consequences to know:
bumping
CODEX_BRIDGE_REV, or anything that remounts the row, restarts the engine and invalidates openresume_sessionids; the bridge says so explicitly in that error.concurrent delegations multiplex over the one engine by JSON-RPC id; Codex tags its
codex/eventnotifications with_meta.requestId, so a call only ever sees its own run.
Policy is read from the event stream, not inferred. The engine emits session_configured with permission_profile, approval_policy, cwd, model and rollout_path. The proxy compares that against what was asked and cancels the running turn on a mismatch (notifications/cancelled), so an over-privileged run stops instead of being reported after the fact. codex-reply does not re-emit the event, so a resumed turn is vouched for by the ledger entry recorded when its thread was created; a thread the ledger doesn't know is reported unverifiable.
Activity comes from the same stream: item_completed for CommandExecution (argv + exit code + output), FileChange (the whole patch), WebSearch, McpToolCall, plus token_count total usage and the account rate-limit window.
Case | Result |
read-only delegation, policy read-back | pass — |
resume the same thread | pass — answered from context, |
resume a read-only thread as | refused before spawn — |
| pass — Codex created the file itself, |
| refused — |
engine hygiene | 1 proxy + 1 engine, no leftovers |
Cost note, measured: a fresh thread on the official engine starts at ~22k tokens even for a one-command task, because mcp-server loads the skills and plugin context into every new session (codex exec cost 574 tokens for the same trivial prompt). Resuming is incremental (a second turn showed 33k total against 22k at creation). For many tiny pings the older codex exec path is cheaper; for anything multi-turn or write-scoped this one is worth it.
Reverse hand: Codex → DSH (dsh-inbound-mcp.mjs)
Codex has no ACP client, so the way in is the other half of the same protocol pair: this bridge is an MCP server that Codex mounts, and it speaks ACP to dsh --profile acp — the supported automation surface for driving a persistent DSH agent.
Tools exposed to Codex: dsh_run(task, cwd?, timeout_seconds?), dsh_reply(session, task), dsh_sessions(limit?). One persistent dsh --profile acp child serves them all, and unlike Codex threads, ACP sessions survive a bridge restart (session/list / session/resume).
Mount it from a Codex config layer ($CODEX_HOME/<name>.config.toml, used with codex exec -p <name>):
[mcp_servers.dsh]
command = "C:\\Program Files\\nodejs\\node.exe"
args = ["D:\\DeepSeek\\dsh-codex-delegate-mcp\\dsh-inbound-mcp.mjs"]
cwd = "D:\\DeepSeek"
startup_timeout_sec = 20
tool_timeout_sec = 900
default_tools_approval_mode = "approve"
env_vars = ["DSH_DELEGATE_DEPTH", "DSH_INBOUND_ROOTS", "DSH_HOME", "PATH", "HOME", "USERPROFILE", "SystemRoot", "COMSPEC", "TEMP", "TMP", "APPDATA", "LOCALAPPDATA", "CODEX_HOME"]
[mcp_servers.dsh.env]
DSH_INBOUND_ROOTS = "D:\\DeepSeek"
DSH_HOME = "C:\\Users\\Administrator\\AppData\\Roaming\\dsh-desktop\\harness"Two Codex defaults cost me a failed round each, so take them literally:
default_tools_approval_mode— an MCP tool call wants approval andcodex exechas nobody to ask, so the call dies asuser cancelled MCP tool call(stderrmcp: dsh/dsh_run (failed)) while the bridge is perfectly healthy. Easy to misread as a bridge bug.env_vars— Codex does not hand an MCP child its full environment. WithoutDSH_DELEGATE_DEPTHin that list the bridge reads depth 0 and the recursion guard silently never fires. The bridge now printsDelegation depth seen by this bridge: Nin every result so this can't fail quietly again.
Two-tier mounting (decided 2026-09-08)
The same [mcp_servers.dsh] block now lives in both places, with different approval modes on purpose:
Where | Approval | Effect |
Codex base |
| any interactive Codex session sees the tools, and each |
|
| unattended |
Rationale: merging into the base config makes the reverse hand reachable from every Codex session, which is exactly the surface you do not want callable silently by file contents (prompt injection). prompt in the base layer makes it loud; the overlay re-opens it for automation you explicitly start. Delete the [mcp_servers.dsh] block from config.toml to withdraw entirely (backup: config.toml.bak-before-dsh).
Audit trail
Every inbound call appends one JSON line to dsh-inbound-audit.jsonl next to the bridge (override with DSH_INBOUND_AUDIT, disable with 'none'): timestamp, depth, tool, workspace, task head (200 chars), reply head, ok/blocked. Both a successful run and a guard refusal are recorded — this is the "who asked for what, when, and what happened" log to review when a session acted on its own.
Ring break, two independent layers
DSH → Codex → DSH → Codex has no bottom unless something refuses. Two separate things refuse:
Depth marker. The forward bridge spawns Codex with
DSH_DELEGATE_DEPTH = own depth + 1. The inbound bridge refuses any call at depth ≥DSH_INBOUND_MAX_DEPTH(default 0) unlessDSH_INBOUND_ALLOW_NESTED=true, and the refusal states its own reason so it can't be mistaken for a crash.Composition. The inbound bridge starts
dsh --profile acp, and theacpprofile does not carry themcp-codex-delegaterow (verified withdsh --profile acp --dump-config). An inbound DSH agent has nomcp__codex__*tool to reach for, guard or no guard.
Permission for inbound work
Measured here: an inbound task runs workspace-writable inside the requested cwd, which must sit inside DSH_INBOUND_ROOTS — a delegated DSH agent created reverse-write-check.txt with no approval round-trip. Anything beyond that arrives as a *_request_permission, which the bridge answers deny plus a NEEDS USER APPROVAL (denied by bridge): … line so it reaches a human; the bridge never escalates on its own. Honest caveat: that deny path is implemented but was never exercised, because no inbound agent asked.
Case | Result |
Codex calls | pass — |
same call at | refused — |
inbound write inside the root | pass — file created by the inner DSH agent, content checked on disk |
MCP boot without | failed with |
guard without | silently depth 0 → guard never fires (documented above) |
This server cannot be deployed
Maintenance
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Pre-execution governance for AI agents. Deterministic PASS/FAIL/REVIEW verdicts, replayable proof.
Supervised API-write gateway for AI agents with policy, human approval and execution receipts.
A paid remote MCP for OpenAI Codex agent coordination MCP, built to return verdicts, receipts, usage
Related MCP Servers
- AlicenseAqualityAmaintenanceA local STDIO MCP server that bridges Codex to the official DeepSeek Harness (DSH) Web Host, enabling supervised DSH collaboration through session management, delegation, and monitoring tools.139MIT
- AlicenseAqualityBmaintenanceEnables AI coding agents like Claude Code or Codex to delegate tasks to a DeepSeek Harness subagent with its own context window, providing tools for task delegation, result waiting, continuation, and supervision with sandboxed execution.6MIT
- AlicenseNot gradedqualityBmaintenanceAn MCP server that bridges Codex with DeepSeek Harness to delegate coding tasks into isolated worktrees, providing profile-based model routing, single/multi-agent orchestration, and reviewable artifacts.12 npm1Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables controlled delegation of tasks to local coding-agent CLIs and the Manus API, with strict sandboxing, approval tracking, and remote-egress safeguards.5 npmMIT