feat(core,cli): M3c — MCP client (stdio) + /mcp slash + REPL wire-up#10
Merged
Conversation
…L wire-up
Implements docs/DEVELOPMENT_PLAN.md §3.3 (stdio subset). HTTP/SSE/OAuth/
headersHelper/Elicitation/serve are M3c-ext (next PR).
What ships
----------
- packages/core/src/mcp/client.ts (~120 lines)
· connectMcpServer(name, config) — spawn server, init, list tools, return
handle with ToolHandler[] registered as
`mcp__<server>__<tool>`
· connectAllMcpServers(servers) — bulk + per-server failure isolation,
supports enabledOnly + disabled filters
· closeAllMcpServers(handles) — graceful shutdown via Promise.allSettled
- packages/core/src/mcp/index.ts — re-exports + module docstring listing
what's in vs deferred
- Top-level @deepcode/core index — exports mcp surface
- apps/cli/src/commands.ts — /mcp slash command lists connected
servers + tool counts + connection errors
- apps/cli/src/repl.ts — on startup, connectAllMcpServers from
settings.mcpServers (respecting
enabledMcpjsonServers / disabledMcpjsonServers);
register every MCP tool into ToolRegistry;
cleanup on exit
Dep change
----------
- @deepcode/core depends on @modelcontextprotocol/sdk ^1.29.0
Tests (6 new, 264 total)
------------------------
- src/mcp/client.test.ts spawns small disk-based MCP server scripts (importing
the SDK by absolute path, since /tmp lacks node_modules). Covers list-tools,
call-tool, failure isolation, enabled/disabled filters, missing-command.
Total: 223 core + 41 cli + 7 skipped = 264 passed / 0 failed.
Verified
--------
pnpm typecheck → green
pnpm test → 264 passed / 0 failed
CLI bin --help → still works
Docs
----
- docs/milestones/M3c-mcp.md — what shipped, what M3c-ext adds
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oratis
added a commit
that referenced
this pull request
May 28, 2026
Summary of this overnight run (PR #10-15): - M3c MCP stdio + /mcp slash - M3c compaction + StatusLine + CLI flag wiring - M3c-ext http/prompt hooks + if field + ApiKeyHelperRefresher + auto-compact - M3.5 sandbox subsystem (macOS sandbox-exec + Linux bwrap) - 15 built-in SKILL.md + effort-bench.ts + release.yml + BEHAVIOR_PARITY.md - M5.1 plugin subprocess + JSON-RPC + capability passing + token+env-strip Plus honest accounting of what's NOT done: - M6 Mac client (zero code) - M7 file panel UI (depends on M6) - M8 Vim/voice/headless polish - M5.2 plugin live wire-up + marketplace - M3.5 attack vector test suite - /init multi-phase + auto classifier mode + mcp_tool/agent hooks Total: ~65-70% of v1 scope on main. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oratis
added a commit
that referenced
this pull request
Jun 1, 2026
… of #10) (#142) * test(sandbox): real-kernel bwrap integration tests + Linux CI setup Step 1 of the §3.9a network-allowlist work: prove the Linux sandbox actually sandboxes on a real kernel (so far only ARG GENERATION was tested), and stand up the CI Linux harness the slirp4netns selective-allowlist work (step 2) will build on. - bwrap-integration.test.ts: spawns the real bwrap-wrapped command and asserts rw-cwd writes succeed, /etc writes fail (ro), /usr is readable, and deny-all network (allowedDomains: []) blocks outbound. Gated on `bwrap` present → runs on the Linux CI runner, skips on macOS/dev. - ci.yml: on Linux, apt-install bubblewrap + slirp4netns + curl and relax Ubuntu 24.04's unprivileged-userns AppArmor restriction so bwrap can unshare. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sandbox): correct contradictory assertion in bwrap e2e write test The pre-existing "blocks writing outside the bound cwd" test was dormant in CI (no bwrap installed) until the new Linux sandbox-tools step activated it. Its own comment notes that /tmp inside the sandbox is a fresh tmpfs, so a write there *succeeds* (exit 0) into that ephemeral, isolated filesystem — yet the test also asserted a non-zero exit. The real security property is that the write never reaches the HOST, which the `exists === false` check already verifies. Drop the contradictory exit-code assertion; a genuine read-only-bind denial (non-zero exit) is covered by bwrap-integration.test.ts (/etc write). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
oratis
added a commit
that referenced
this pull request
Jun 1, 2026
Implements the Linux selective network allowlist (the last gap in §3.9a's
sandbox). When sandbox.network.allowedDomains is a non-empty allowlist,
spawnNetworkSandbox (netns.ts) orchestrates:
1. the allowlisting DNS proxy (dns-proxy.ts) on 127.0.0.1:53 — forwards
allowed lookups upstream, returns NXDOMAIN for everything else;
2. bwrap --unshare-net --uid 0 --gid 0 with our resolv.conf bound (at the
symlink-resolved real path) and --info-fd/--block-fd for PID handoff +
readiness gating;
3. slirp4netns --configure --disable-dns attached to bwrap's netns by PID,
giving rootless userspace NAT (tap0, 10.0.2.100/24, gateway 10.0.2.2 →
host loopback where the proxy listens).
The decisive detail: --uid 0 --gid 0 maps the host user to root inside
bwrap's userns, which is what lets slirp (the host user, owner of that
userns) gain CAP_SYS_ADMIN on entry and setns() into the netns — without it
setns(CLONE_NEWNET) is EPERM.
Threat model: DNS-NAME allowlisting (raw-IP dials bypass it) — adequate for
the git/npm/pip-over-https agent workload, and --disable-dns closes the
10.0.2.3 bypass. Requires binding :53 (CAP_NET_BIND_SERVICE or a relaxed
ip_unprivileged_port_start); when unavailable, callers fail CLOSED via
NetworkSandboxUnavailable rather than running unrestricted.
Verified on the Linux CI runner by netns-integration.test.ts (gated on
DC_SANDBOX_NET_TEST + bwrap + slirp4netns): an allowlisted domain returns
HTTP 200 while a non-allowlisted domain fails to resolve. The mechanics were
proven first via a throwaway CI PoC (now removed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
oratis
added a commit
that referenced
this pull request
Jun 1, 2026
…10 step 2 (#143) * chore(sandbox): diagnostic PoC for slirp4netns net-allowlist mechanics TEMPORARY. Adds scripts/sandbox-net-poc.sh + a continue-on-error Linux CI step that exercises the full bwrap --unshare-net + slirp4netns + DNS-proxy allowlist flow on a real kernel, printing diagnostics. This nails down the exact info-fd/ready-fd handshake, host-loopback DNS routing, and port-53 bindability before they are encoded in packages/core/src/sandbox/netns.ts. Both the script and the CI step are removed once the TS orchestrator + integration test land. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sandbox): make net PoC hang-proof (sleep readiness + watchdog) The FIFO-based readiness handshake deadlocked in CI (host open(O_WRONLY) blocks forever when the in-sandbox reader and host writer don't share the inode across the bind mount). Replace it with a 3s sleep window inside the sandbox (slirp configures in <1s) plus a 45s background watchdog that hard-kills the sandbox and a trap that always tears down slirp/proxy/tmp. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sandbox): fix resolv.conf bind (dangling symlink) + --disable-dns bwrap couldn't bind our resolv.conf onto /etc/resolv.conf because on the runner it's a dangling symlink (→ /run/systemd/resolve/stub-resolv.conf, not mounted in the sandbox). Bind our file at the readlink-resolved real path so the preserved symlink leads to it. Add slirp4netns --disable-dns to close the 10.0.2.3 bypass (all resolution must traverse our allowlist). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sandbox): slirp --userns-path to fix setns EPERM into bwrap netns slirp4netns failed with setns(CLONE_NEWNET): Operation not permitted — bwrap's net namespace is owned by bwrap's child user namespace, which the host-user slirp process has no CAP_SYS_ADMIN over. Pass --userns-path=/proc/<pid>/ns/user so slirp enters that userns (where it is root) before the netns. resolv.conf bind now confirmed working. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sandbox): PoC — bwrap --uid 0 + README-exact slirp + ns diagnostics --userns-path did not resolve setns(CLONE_NEWNET) EPERM. Dump the namespace topology (child user/net ns links + uid_map/gid_map + lsns) to see whether bwrap nests the userns such that the netns is owned by a parent userns slirp can't gain caps over. Also try the slirp4netns README-exact incantation: bwrap --uid 0 --gid 0 (root-mapped userns) + plain `slirp4netns --configure <pid> tap0` (no --userns-path/--disable-dns). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sandbox): slirp4netns selective per-domain network allowlist (#10) Implements the Linux selective network allowlist (the last gap in §3.9a's sandbox). When sandbox.network.allowedDomains is a non-empty allowlist, spawnNetworkSandbox (netns.ts) orchestrates: 1. the allowlisting DNS proxy (dns-proxy.ts) on 127.0.0.1:53 — forwards allowed lookups upstream, returns NXDOMAIN for everything else; 2. bwrap --unshare-net --uid 0 --gid 0 with our resolv.conf bound (at the symlink-resolved real path) and --info-fd/--block-fd for PID handoff + readiness gating; 3. slirp4netns --configure --disable-dns attached to bwrap's netns by PID, giving rootless userspace NAT (tap0, 10.0.2.100/24, gateway 10.0.2.2 → host loopback where the proxy listens). The decisive detail: --uid 0 --gid 0 maps the host user to root inside bwrap's userns, which is what lets slirp (the host user, owner of that userns) gain CAP_SYS_ADMIN on entry and setns() into the netns — without it setns(CLONE_NEWNET) is EPERM. Threat model: DNS-NAME allowlisting (raw-IP dials bypass it) — adequate for the git/npm/pip-over-https agent workload, and --disable-dns closes the 10.0.2.3 bypass. Requires binding :53 (CAP_NET_BIND_SERVICE or a relaxed ip_unprivileged_port_start); when unavailable, callers fail CLOSED via NetworkSandboxUnavailable rather than running unrestricted. Verified on the Linux CI runner by netns-integration.test.ts (gated on DC_SANDBOX_NET_TEST + bwrap + slirp4netns): an allowlisted domain returns HTTP 200 while a non-allowlisted domain fails to resolve. The mechanics were proven first via a throwaway CI PoC (now removed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sandbox): swallow stream errors on netns teardown The integration test's assertions passed but the run failed: SIGTERM-ing slirp4netns / bwrap in close() reset their stdio pipes, emitting `read ECONNRESET` with no 'error' listener → vitest flagged 2 unhandled errors. Attach no-op 'error' handlers to both child processes and all their stdio streams so teardown resets are absorbed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
oratis
added a commit
that referenced
this pull request
Jun 1, 2026
…144) BashTool now routes commands through spawnNetworkSandbox when network.allowedDomains is a non-empty allowlist on Linux. If the slirp sandbox can't be set up (e.g. the DNS proxy can't bind 127.0.0.1:53), it FAILS CLOSED — re-runs under deny-all-net with a clear note — rather than running unrestricted. Background commands always fail closed (the slirp helper can't safely outlive the turn). - netns.ts: add pure helpers needsNetworkSandbox() (linux + enabled + non-empty allowlist) and denyAllNetwork() (fail-closed config). - bash.ts: foreground net path (capture/timeout/abort via the handle), fail-closed fallback, background deny-all; shared summarize() helper. - Tests: netns.test.ts (decision + config helpers, runs everywhere) + bash.test.ts wiring tests using an injected fake spawner (net path, fail-closed fallback, background) — no real bwrap needed. - docs/security-model.md: document the Linux allowlist, its DNS-name threat model, the :53 requirement, and the fail-closed behavior. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mcp__<server>__<tool>into the same ToolRegistry the 6 P0 tools use./mcpslash command + REPL auto-connect from settings.mcpServers.Release notes
release-notes:featureCo-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com