ddx-term-mcp
by Dudoxx
README.md
# dudoxx-ai-terminal
**Shared multi-terminal bridge** — one tmux session, many addressable terminals,
attached by a **human** (web / xterm.js) and an **AI agent** (MCP) *simultaneously*,
both looking at the exact same terminals. Architecture in one line: a single
pinned-size `tmux` session is the shared state; a NestJS **broker** owns the
canonical terminal registry and fans output to the web UI per-terminal over
WebSocket; an **MCP** server lets Claude shell out to the same tmux session (it
holds no PTY); a Next.js **web** app renders the terminals with xterm.js.
## Packages
| Package | Role | Port / transport |
|---|---|---|
| `@ddx/term-contract` (`packages/ddx-term-contract`) | Shared zod types — WS frames, MCP tool I/O, terminal/session descriptors | library (ESM+CJS+types) |
| `ddx-term-broker` | Human channel + canonical state: tmux control-mode attach, registry, REST CRUD, per-terminalId WS | HTTP/WS **13330** (`DDX_TERM_BROKER_PORT`) |
| `@dudoxx/ddx-term-mcp` | Agent channel: MCP stdio server, thin tmux client (no PTY) | stdio (`node dist/server.js`) |
| `ddx-term-web` | Next.js 16 xterm.js UI, one tab per terminalId | HTTP **13340** (`DDX_TERM_WEB_PORT`) |
> Ports default to the high **133XX** band on purpose — clear of `3000`/`5173`/`8080` so the
> bridge never collides with your own running codebases. Override via env or a `.env` file
> (see [Configuration](#configuration)).
> `ddx-cli-py` / `ddx-cli-ts` are interactive e2e-target fixtures, **not** workspace
> packages — they are never built or published from here.
## Bring-up
The normal path is **agent-driven** — you do not start a dev server by hand. Build once,
register the MCP (below), then call any `term_*` verb: the MCP server's supervisor spawns
the broker (`13330`) and web (`13340`) automatically and attaches to them.
```sh
pnpm install
pnpm build # builds all packages incl. ddx-term-mcp/dist/server.js
# or just the MCP server + its contract dep:
pnpm --filter @dudoxx/ddx-term-mcp... build
```
Then open the web UI at **http://localhost:13340** to watch the same terminals the agent
drives. Lock files at `~/.ddx-term/{broker,web}.lock` make the stack a machine-wide
singleton; set `DDX_TERM_WEB=0` to run broker-only (headless).
> **Hacking on the broker/web themselves?** `pnpm dev` still runs both concurrently via
> turbo (`broker 13330 + web 13340`) for a live edit loop — but it is no longer required for
> normal agent use. See [`SETUP.md`](./SETUP.md).
## Register the MCP server (agent channel)
Add this to a Claude Code project's `.mcp.json` (or your Claude Desktop config).
A copy lives in [`.mcp.json.example`](./.mcp.json.example) — replace `/abs/path`
with this repo's absolute path:
```json
{
"mcpServers": {
"ddx-term": {
"command": "node",
"args": ["/abs/path/dudoxx-ai-terminal/ddx-term-mcp/dist/server.js"],
"env": {
"DDX_TERM_SOCKET": "/tmp/ddx-term.sock",
"DDX_TERM_SESSION": "ddx-shared",
"DDX_TERM_DEFAULT": "t01",
"DDX_TERM_BROKER_PORT": "13330",
"DDX_TERM_WEB_PORT": "13340"
}
}
}
}
```
| Env | Default | Purpose |
|---|---|---|
| `DDX_TERM_SOCKET` | `/tmp/ddx-term.sock` | tmux `-S` socket of the shared session |
| `DDX_TERM_SESSION` | `ddx-shared` | session hosting all terminals (windows) |
| `DDX_TERM_DEFAULT` | `t01` | terminalId used when a verb omits `terminalId` |
| `DDX_TERM_BROKER_PORT` | `13330` | broker HTTP/WS port |
| `DDX_TERM_WEB_PORT` | `13340` | web UI HTTP port |
| `DDX_TERM_HOST` | `127.0.0.1` | bind/connect host (loopback by design) |
| `DDX_TERM_WEB` | (unset → web on) | set `0` to run broker-only (no web spawn) |
The `env:` block is the **per-client** override layer — it wins over `.env` files. The full
table (read caps, allow-list, supervisor-written URLs) is in [`DEPENDENCIES.md`](./DEPENDENCIES.md).
### Global install (every Claude Code session)
The package is `bin`-shaped (`ddx-term-mcp` → `dist/server.js`) with a `prepare`
script that builds on install. To register it for **all** projects, add it to your
user-scope MCP config via the CLI (run from this repo so `$PWD` resolves):
```sh
pnpm -F @dudoxx/ddx-term-mcp build # produces an executable dist/server.js
claude mcp add ddx-term --scope user \
--env DDX_TERM_SOCKET=/tmp/ddx-term.sock \
--env DDX_TERM_SESSION=ddx-shared \
--env DDX_TERM_DEFAULT=t01 \
--env DDX_TERM_BROKER_PORT=13330 \
--env DDX_TERM_WEB_PORT=13340 \
-- node "$PWD/ddx-term-mcp/dist/server.js"
```
Verify with `claude mcp list` (look for `ddx-term … ✔ Connected`).
## Configuration
Every setting has a built-in code default and **three override layers** — highest wins, and
`override: false` means an explicitly-set var always beats a file:
1. **The MCP client `env:` block** (`.mcp.json` / `claude mcp add --env`) — per agent / per client.
2. **A project-local `.env`** in the current working directory.
3. **A global `~/.ddx-term/.env`** — applies to every client on the machine.
Copy [`.env.example`](./.env.example) to `.env` (project) or `~/.ddx-term/.env` (global) and
edit. The MCP server loads both files (`src/load-dotenv.ts`) before resolving ports; the
broker and web load them too for the standalone `pnpm dev` path. Example — move the whole
stack onto a different band:
```sh
# ~/.ddx-term/.env
DDX_TERM_BROKER_PORT=14330
DDX_TERM_WEB_PORT=14340
```
`.env` is gitignored; `.env.example` is the committed template.
> **Publishing is wired — `@dudoxx/ddx-term-mcp` ships as a self-contained npm
> package.** Its publish bundle (`build:bundle`, tsup) inlines `@ddx/term-contract`
> from source into a single `dist/server.js` (`noExternal: ['@ddx/term-contract']`),
> keeping only `zod` + `@modelcontextprotocol/sdk` as external runtime deps. That
> bundle is wired as `prepublishOnly`, so the published tarball carries **no
> `workspace:*` reference** — the contract stays `"private": true` and is never
> published separately (by design; it lives in the changeset `ignore[]`). Once a
> version is released to the registry (see **Release & versioning** below), the
> `npx -y @dudoxx/ddx-term-mcp` form resolves. Until the first registry release,
> register by absolute path as above.
The MCP server is a **thin tmux client — it never owns a PTY** (no `node-pty`); it
shells out to tmux against the same session the broker and the human are attached
to. That shared-state property is the whole point: a command the agent types is
visible live in the web UI.
## Attach natively from a real terminal emulator (human channel, no browser)
Because the bridge IS a standard tmux session, you can also attach with a native
tmux control-mode client — iTerm2 and WezTerm render tmux `-CC` natively:
```sh
tmux -CC attach -t ddx-shared
```
This opens the shared terminals as native tabs/windows in iTerm2 / WezTerm,
alongside the web UI and the agent — all three see the same terminals.
## Verification
```sh
pnpm typecheck # turbo run typecheck across all packages
pnpm lint
pnpm test # turbo run test (unit + e2e; e2e spins throwaway tmux on a temp socket)
```
## Release & versioning (Changesets → npm)
Only **`@dudoxx/ddx-term-mcp`** publishes to public npm; broker, web, and the
contract are `"private": true` and listed in `.changeset/config.json` `ignore[]`.
```sh
pnpm changeset # 1. describe the change + pick a bump (patch/minor/major)
git commit && push # 2. open a PR that includes the .changeset/*.md
```
On merge to `main`, `.github/workflows/release.yml` runs Changesets' action:
1. It opens (or updates) a **"Version Packages" PR** that bumps `package.json`,
writes `CHANGELOG.md`, and consumes the changeset files.
2. Merging **that** PR triggers `pnpm release` → `build:bundle` (tsup, inlines the
contract) → `changeset publish` → `npm publish` of any package whose version changed.
The publish authenticates via the repo secret **`NPM_TOKEN`** (an npm automation
token with publish rights to the `@dudoxx` scope) and emits provenance
(`NPM_CONFIG_PROVENANCE=true`). After the first release lands, the
`npx -y @dudoxx/ddx-term-mcp` registration form resolves.
> Verify a tarball before publishing: `cd ddx-term-mcp && pnpm build:bundle &&
> npm pack --dry-run` — it must list `dist/server.js` (self-contained, no
> `workspace:*`), `package.json`, `README.md`, `INSTALLATION.md`, `LICENSE`, and
> the two `assets/*.png`.
For the full release walkthrough, bump-type guidance, and CI details see
[`CLAUDE_PUBLISHING.md`](./CLAUDE_PUBLISHING.md) and
[`ddx-documentation/05-publishing/release-flow.md`](./ddx-documentation/05-publishing/release-flow.md).
---
Dudoxx UG / Acceleate Consulting - Walid Boudabbous <walid@acceleate.com>
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues