coding-tools-conductor
v0.1.2
Published
A TypeScript orchestration layer for coding-tools-mcp.
Maintainers
Readme
Coding Tools Conductor
Coding Tools Conductor (ctc) is an orchestration layer that sits above
coding-tools-mcp. It exposes a
single MCP server to your model and talks to the lower Python MCP server as an
MCP client, adding the things a coding agent needs around raw file and git
operations: workspace lifecycle, isolated git worktrees, human review
checkpoints, handoff state, remote access, and aggregation of other MCP servers.
Run ctc for an interactive terminal UI, or ctc start for a headless MCP
server that any model client can connect to over stdio.
Contents
- Installation
- Quick start
- How it works
- The terminal UI
- Backends
- Workspaces and review
- Project context and skills
- Baton handoff
- Remote access
- Extra MCP servers
- ChatGPT Apps adapter
- Command reference
- Model-facing tools
- Configuration
- Runtime environment and shutdown
- Telemetry
- Development
Installation
Requires Node.js 22+. Install the CLI globally:
npm install -g coding-tools-conductor # provides the `ctc` commandOr run it once without installing:
npx coding-tools-conductor --helprun-ctc is a name alias for the same package and the same ctc command —
install one or the other, not both. The project is in beta; add @beta to pull
the newest pre-release (npm install -g coding-tools-conductor@beta).
You do not need to install the lower coding-tools-mcp server yourself. If
it is not already on your PATH, ctc runs it with uvx coding-tools-mcp (uv
fetches the published PyPI package on first use), or you can point ctc at a
Docker image or a remote HTTP endpoint instead — see Backends.
Quick start
ctc # open the TUI and start a session for the current repo
ctc /path/to/repo # same, for another repoLaunching ctc opens an interactive terminal UI and starts a live session on
the target repository. Point your model client at the MCP endpoint it prints,
edit the repo through the model, and use /diff to review changes before you
keep them. Quitting closes the session and stops the backend.
For a headless server with no UI (for example, embedding ctc in another host):
ctc start /path/to/repoHow it works
Conductor is a thin, opinionated layer with a strict division of responsibilities:
- Single atomic filesystem, process, and git operations belong to
coding-tools-mcp. Conductor forwards these instead of rebuilding them. - Cross-tool state — policy, workspace lifecycle, human review checkpoints, and handoff flows — belongs to Conductor.
- Conductor reaches the lower layer only through MCP. It does not import
the Python project or run shell/git directly. The sole exception is the
workspace/module, where managed git worktree creation and cleanup must run in the source repository before the lower server is pointed at the worktree.
A CI lint enforces boundary 3 by keeping execa out of every module except
src/workspace/ and the setup wizard.
Primary backend tools are re-exposed to the model without prefixes (subject to
profile allow/deny policy). Conductor adds its own workspace, review, and baton
tools on top, and any additional MCP servers are re-exposed with a <server>__
prefix. Every proxied tool call emits a JSONL audit event under ~/.ctc/logs/.
The terminal UI
Launching ctc is the session: the TUI opens a workspace session for the
repo (direct mode by default) and quitting closes it — the workspace record is
stamped closed and the backend stops, the way Claude Code or Codex CLI treat a
run. A separate observe mode, ctc tui <session-id>, renders an external
ctc start session read-only and answers its permission prompts without
hosting a session of its own.
Transcript. Activity renders as an append-only transcript that flows into
terminal scrollback, in the style of Claude Code: each tool call is a ● line
with a dimmed ⎿ result (or a red error), review checkpoints render as inline
diff cards, and notices appear as ✓ / ✗ / · notes. Updates are
event-driven — the TUI watches the active session's log with a dirty-checked
snapshot store rather than repainting on a timer.
Commands. Typing / opens a navigable menu of every command, grouped by
area (Sessions, Review, Configure, Interface). It fuzzy-matches as you type
(/dr finds doctor), highlights matched characters, and wraps at the ends —
arrows to choose, Tab to complete, Enter to run. See the
TUI command reference for the full list.
Composer. The input supports the usual readline editing keys: Ctrl+A/E to jump to line start/end, Ctrl+W/U/K to delete by word or to the line edges, and Ctrl+←/→ or Alt+←/→ to move by word.
Panels. Bounded panels open over the live region for /diff, /baton,
/approvals, /config, /inspect (also Ctrl+O), /doctor, and /help; ↑/↓
scroll by line, ←/→ (or PgUp/PgDn) page while the composer is empty, and Esc
closes. /clear resets the transcript, and Ctrl+C must be pressed twice to
quit so a stray interrupt cannot tear down the live session.
Approvals. If the model calls the lower request_permissions tool while the
TUI is attached, Conductor pauses the request and shows an approval prompt with
selectable options (y/n, 1/2, arrows + Enter; left/right walk the queue
when several requests are pending; Esc denies). Without an attached TUI, the
request falls back to the lower backend's existing permission flow.
Backends
ctc start connects to the lower MCP server in one of three modes, selected
with --backend or during ctc setup:
| Mode | What it does |
| --- | --- |
| stdio (default) | Spawns coding-tools-mcp --stdio --workspace <path>, or a profile/CLI-supplied command. When no command is configured, resolution mirrors how esbuild finds its platform binary: a coding-tools-mcp on PATH wins, otherwise ctc falls back to uvx coding-tools-mcp. If both are missing, startup fails with install guidance instead of a bare spawn ... ENOENT. |
| docker | Runs the published sandbox image (ghcr.io/xytom/coding-tools-mcp-sandbox:latest, override with CTC_BACKEND_DOCKER_IMAGE or ctc setup) with the workspace bind-mounted at /workspace, via docker run --rm -i --init. The image is pulled on first start. |
| http | Connects to a Streamable HTTP MCP endpoint with an optional bearer token read from an environment variable. |
ctc start /path/to/repo --backend stdio
ctc start /path/to/repo --backend docker
ctc start /path/to/repo --backend-command-json '["uvx","coding-tools-mcp","--stdio","--workspace","/path/to/repo"]'
ctc start /path/to/repo --backend http --backend-url http://127.0.0.1:8765/mcp --backend-token-env CTC_TOKEN--backend-command accepts a bare command name only; flags after it are parsed
as ctc's own options, so use --backend-command-json for commands that need
arguments.
First starts that download (uvx) or pull (docker) can exceed the 5-minute
control-plane budget on slow networks; raise it with
CTC_BACKEND_CONTROL_TIMEOUT_MS.
Workspaces and review
Workspace mode defaults to direct — the model edits the repo in place.
Worktree isolation is an explicit opt-in (/new --worktree), which opens a
detached git worktree so the model's changes stay off your working tree until
you merge them.
Three model-facing tools drive this:
open_workspaceopens either a direct workspace or an isolated detached git worktree, then points the lower MCP server at that path withset_default_cwd.close_workspacecloses the active workspace and removes managed worktrees when they are clean, or whenforceis explicitly passed.show_changescreates a temporary-index git snapshot, diffs it againstrefs/ctc/review/<session>/baseline,last-shown, orHEAD, and advanceslast-shownafter each review checkpoint.
Only worktree mode requires a git repository. A direct workspace opens in any
directory: when the path is not a repository (or has no commits yet),
open_workspace still succeeds and reports review.available: false with the
reason, and the TUI notes that review checkpoints are off. show_changes
re-checks on each call, so git init plus a first commit turns checkpoints on
mid-session without reopening the workspace.
Human-side workspace commands:
ctc ws list
ctc ws clean --yes
ctc ws clean --force --yes
ctc ws merge <session-id>Inside the TUI, /clean [--force|--yes] mirrors ctc ws clean with an
in-terminal confirmation prompt. Cleaning also prunes closed session records —
their ~/.ctc JSON, log, and approval files are deleted so state stays
bounded; the TUI's active session is always exempt.
Managed worktrees are created inside the repository at
.ctc/worktrees/<session-id> so the workspace-confined lower server can reach
them with workspace-relative paths (it denies absolute paths). The directory is
added to .git/info/exclude automatically, so it never shows up in source-repo
status, diffs, or merges.
Worktree creation and cleanup are the only direct git operations in the core
runtime. Review checkpoints run git through the lower exec_command tool so the
layering boundary stays intact.
Project context and skills
open_workspace returns a context guide alongside workspace metadata:
- Repository-root
AGENTS.md,CLAUDE.md, and.cursorrulesfiles are listed with byte counts; root-level files are inlined up to a fixed safety limit. - Nested instruction files are listed by path so the model knows where deeper rules exist before editing there.
- Workspace skills are discovered from
.ctc/skills/<name>/SKILL.md, with.claude/skills/read as a compatibility fallback.
The model-facing load_skill tool returns the full SKILL.md content for a
skill listed by open_workspace.context.skills.
Baton handoff
The baton is local handoff state written inside the active workspace's
.baton/ directory. Model-facing tools write there and nowhere else:
baton_write_plan(content)writes.baton/plan.md.baton_read_plan()reads.baton/plan.md.baton_update_status(phase, step?, state, note?)writes.baton/status.jsonwith anupdatedAttimestamp.baton_write_report(content)writes.baton/report.md.
The directory also holds an artifacts/ folder for future diff or log
attachments. Add .baton/ to your workspace gitignore when using it.
Inspect the baton from the CLI (ctc baton show /path/to/repo) or the TUI
(/baton).
Remote access
/tunnel start exposes the MCP server over a free try.cloudflare.com tunnel
so remote connectors (ChatGPT, Claude, and other MCP clients) can reach it. A
tunnel requires the live session — one is started automatically, and /new
restores it after a /close.
If cloudflared is already installed the tunnel starts immediately. Otherwise
the TUI opens a picker of the ways this host can run one:
- Use wrangler (no install) — runs
wrangler tunnel quick-start <url>, using awrangleronPATHornpx wrangler(the firstnpxrun downloads wrangler and can take a minute). Nothing is installed permanently. - Install cloudflared —
brew install cloudflaredon macOS when Homebrew is present, otherwise the official release binary is downloaded to~/.ctc/bin/cloudflared. The managed binary is preferred on later runs, so this is a one-time step.
Both providers surface the same *.trycloudflare.com URL. Hosts with neither
cloudflared nor npx say so plainly instead of hanging.
Endpoints and transports
Model clients connect to the stable /mcp endpoint (locally
http://127.0.0.1:<port>/mcp, through a tunnel https://<tunnel-host>/mcp).
The port is persisted in the workspace profile, so a client configured once
keeps working across ctc restarts. /mcp serves the live session;
/mcp/<session-id> pins a specific id (a /new replacement mints a new one).
Each client that POSTs an initialize request gets its own MCP session (routed
by the Mcp-Session-Id header), so any number of clients can connect,
reconnect, and terminate independently.
Conductor speaks both remote MCP transports, so connectors that only implement
the legacy 2024-11-05 HTTP+SSE transport connect too: a GET /sse — or a
GET /mcp with an SSE Accept and no Mcp-Session-Id — opens the old
handshake (endpoint event, messages POSTed to /messages?sessionId=...,
keepalive pings so free tunnels do not idle the stream out). Common URL
mistakes stay routable: the bare tunnel origin / aliases /mcp, and a plain
browser/curl GET on /, /mcp, or /.well-known/mcp.json returns a small
server card describing the endpoints and auth mode (the card and /.well-known
stay readable without credentials; everything else is gated). When no session
is live, MCP endpoints answer 503 rather than 404, so clients report a
temporarily unavailable server instead of a wrong URL.
Tunnel authentication
The tunnel gate supports three authentication methods, chosen in ctc setup
(or /config tunnel-auth <method>) and reported in the server card's auth
field:
| Method | How clients authenticate |
| --- | --- |
| bearer (default) | Clients send Authorization: Bearer <token>. Best where you paste a token or custom header by hand. Existing profiles keep working unchanged. |
| url-token | The token rides in the query string, e.g. …/mcp?token=<token>, for clients that cannot set a custom Authorization header. The parameter name defaults to token (--tunnel-token-param). Note it can appear in proxy/access logs. |
| oauth | The MCP-standard OAuth 2.1 + PKCE flow, built on the MCP SDK's authorization server. Conductor advertises /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource, supports dynamic client registration, and gates a browser consent page with a single owner password. Use this for connectors that auto-discover OAuth (e.g. ChatGPT and Claude). |
Bearer tokens and the OAuth owner password are generated fresh each
/tunnel start and printed once, or sourced from an env:<NAME> reference
(--tunnel-token-env, --tunnel-owner-password-env) for a stable secret across
restarts. Secrets are never written into the profile — only the method and env
references are.
Extra MCP servers
Conductor can aggregate additional third-party MCP servers (GitHub MCP,
Playwright MCP, remote Streamable HTTP servers, …) behind the same model-facing
endpoint. The primary coding-tools-mcp backend stays authoritative: its tools
remain unprefixed, open_workspace, review checkpoints, baton files, and
request_permissions bind to it exclusively, and a primary connection failure
still fails startup. Extra servers are tools-only — each tool is exposed as
<server>__<tool> (github__create_issue, playwright__browser_click) and
calls route back to the owning server.
Configuration merges two places:
- the workspace profile (
~/.ctc/profiles/<repo-hash>.json) under anmcpServersrecord — personal, implicitly trusted; - a shareable
<repo>/.ctc/mcp.jsoncommitted with the repository.
{
"mcpServers": {
"github": { "command": "docker", "args": ["run", "-i", "--rm", "ghcr.io/github/github-mcp-server"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "env:GITHUB_TOKEN" } },
"playwright": { "command": "npx", "args": ["@playwright/mcp@latest"] },
"remote": { "url": "https://example.com/mcp", "headers": { "Authorization": "env:REMOTE_AUTH" } }
}
}Entries are stdio (command, args, env) or Streamable HTTP (url,
headers), plus optional disabled and per-server allow/deny tool policies
(unprefixed names). Server names must match [A-Za-z0-9_-]+ so prefixed tool
names stay MCP-safe. env and headers values support env:<NAME> references,
resolved at connect time — recommended over literals so .ctc/mcp.json never
carries secrets. Extra stdio servers spawn with a minimal default environment
plus the configured env only. On a name collision the profile entry replaces
the workspace entry wholesale (personal over shared), and exposed tool names
resolve conductor > primary > extras, with shadowed names reported per server.
Trust boundary. Workspace-declared servers are untrusted: a cloned
repository could ship a malicious stdio command, so they start
disabled (untrusted) until approved with /mcp trust <name> (records a
fingerprint of the entry; if the repo later edits it, trust resets) or
per-session with ctc start --trust-workspace-mcp. Profile-declared servers are
trusted as your own configuration.
Failure semantics. Extra servers never block startup. A broken command or
unreachable URL becomes a per-server error status with background reconnect
(250 ms → 5 s backoff, 8 s connect timeout) while the session keeps serving.
Conductor advertises tools.listChanged and broadcasts
notifications/tools/list_changed when an extra server connects, drops, or is
toggled, so clients pick up tool changes mid-session. Config files are read once
at session start; edit them and open a new session, or use /mcp reconnect for
connection-level retries.
In the TUI, /mcp opens a live status panel and /mcp enable|disable|reconnect|trust <name>
manages servers (only trust writes the profile). ctc doctor adds one
mcp:<name> check per configured server. Note for mixed versions: older ctc
builds rewrite profiles through a stricter schema and drop the new
mcpServers/trustedWorkspaceMcp fields on their next profile write.
ChatGPT Apps adapter
The ChatGPT Apps adapter is optional and isolated under
src/adapters/chatgpt/. Core behavior does not depend on it, and it is disabled
unless the workspace profile contains { "adapters": ["chatgpt"] } (or you pass
--adapter chatgpt during setup).
When enabled, Conductor advertises MCP widget resources and adds ChatGPT Apps
metadata to three high-value tools: open_workspace (a workspace/context guide
card), show_changes (a review diff card), and baton_update_status (a baton
progress card). It uses standard MCP Apps metadata (_meta.ui.resourceUri) plus
ChatGPT compatibility aliases such as _meta["openai/outputTemplate"]. It is a
host-specific presentation layer; model-facing tool names and core results stay
unchanged.
Command reference
CLI commands
| Command | Description |
| --- | --- |
| ctc [path] | Open the TUI and start a session for the repo (defaults to the current directory). |
| ctc start [path] | Start a headless conductor MCP session over stdio. |
| ctc tui [session-id] | Observe an external ctc start session read-only and answer its permission prompts. |
| ctc setup [path] | Configure a workspace profile (backend, default mode, tunnel, adapters, tool policy). |
| ctc doctor [path] | Check the saved profile, backend, worktree storage, and git version. |
| ctc ws list | List recorded ctc workspaces. |
| ctc ws clean [--force] [--yes] | Clean recorded worktrees and prune closed session records. |
| ctc ws merge <session-id> | Apply a worktree session's patch back to the source repository. |
| ctc baton show [path] | Render .baton/plan.md, status.json, and report.md. |
| ctc stats [--days N] [--json] | Show local telemetry: latency, reliability, usage, and merge metrics. |
| ctc telemetry <status\|on\|off\|local\|reset\|endpoint\|sentry\|flush> | Inspect or change telemetry collection and upload. |
| ctc --version | Print the ctc version. |
Run ctc <command> --help for the full option list of any command.
TUI commands
Type / in the TUI to open the command menu.
| Group | Command | Description |
| --- | --- | --- |
| Sessions | /new [path] [--worktree\|--direct] [--resume <wt>] | Replace the session: switch repo or isolation mode. |
| Sessions | /close [--force] | Close the session; /new reopens one. |
| Sessions | /merge | Merge worktree changes back into the source repo. |
| Sessions | /clean [--force\|--yes] | Remove idle worktrees and prune closed session records. |
| Review | /diff | Show the latest review checkpoint for this session. |
| Review | /baton | Show the session baton plan, status, and report. |
| Review | /approvals | List pending approvals for this session. |
| Review | /stats [days] | Show local telemetry: latency, reliability, and merge stats. |
| Configure | /mcp [status\|enable\|disable\|reconnect\|trust <name>] | Show and manage extra MCP servers for the session. |
| Configure | /tunnel [start [bearer\|url-token\|oauth]\|stop\|status] | Manage the public MCP tunnel. |
| Configure | /config [backend\|mode\|permission\|tunnel\|tunnel-auth\|reset] … | View or update the repo profile (reset deletes it). |
| Configure | /doctor | Run profile, git, worktree, and backend checks. |
| Interface | /help (/?) | Show TUI commands and key bindings. |
| Interface | /inspect | Open the event inspector — raw JSON for recent events (Ctrl+O). |
| Interface | /details | Reopen the latest error or long output, scrollable. |
| Interface | /clear | Clear the transcript and start a fresh screen. |
| Interface | /quit (/q) | Exit the TUI. |
Model-facing tools
Beyond the proxied backend tools (re-exposed unprefixed) and aggregated servers
(<server>__<tool>), Conductor adds these tools of its own:
| Tool | Purpose |
| --- | --- |
| open_workspace | Open a direct workspace or an isolated worktree and return a project context guide. |
| close_workspace | Close the active workspace; remove a managed worktree when clean (or with force). |
| show_changes | Produce a review checkpoint diff against the session baseline / last-shown / HEAD. |
| load_skill | Return the full SKILL.md content for a workspace skill. |
| baton_write_plan / baton_read_plan | Write / read .baton/plan.md. |
| baton_update_status | Write .baton/status.json with a timestamp. |
| baton_write_report | Write .baton/report.md. |
Configuration
Profiles are stored as JSON under ~/.ctc/profiles/<repo-hash>.json and hold
the backend mode, default workspace mode, tunnel settings, tool policy, adapter
flags, and extra mcpServers. Create or edit one with ctc setup:
ctc setup /path/to/repo
ctc setup /path/to/repo --yes --skip-smoke --default-mode worktree
ctc doctor /path/to/repo
ctc doctor /path/to/repo --skip-backendSecrets are never written into a profile: HTTP bearer tokens, tunnel tokens, and
MCP server credentials are stored as env:<NAME> references (such as
env:CTC_TOKEN) and resolved from the environment at connect time.
Runtime environment and shutdown
Backend PATH. Conductor resolves the user's login-shell PATH once per
process (bash/zsh via $SHELL -ilc; fish and csh get their own probe; 5 s
timeout, cached, CTC_RESOLVING_LOGIN_SHELL=1 set during the probe) and spawns
the primary stdio backend with process.env plus that merged PATH. This keeps
nvm/pyenv/asdf-selected toolchains working when the MCP host that launched ctc
came from a GUI with a minimal PATH — the same approach VS Code uses. Existing
custom PATH entries stay in front; login-shell entries are appended. Extra
stdio MCP servers get the same widening. Set CTC_NO_LOGIN_SHELL_PATH=1 to
disable the probe.
Request budgets. Control-plane calls (initialize, tools/list) time out
after 5 minutes — generous enough to absorb a first-start Docker pull or PyPI
download — and tool calls after 10 minutes. A timeout marks the backend
disconnected and starts the reconnect loop instead of hanging the session.
Override with CTC_BACKEND_CONTROL_TIMEOUT_MS / CTC_BACKEND_TOOL_TIMEOUT_MS.
Shutdown. ctc start exits when its host hangs up (stdin EOF, SIGINT, or
SIGTERM) and tears the spawned lower backend down with it — stdin EOF first
(clean MCP stdio shutdown; lets a docker run --rm -i backend stop and
auto-remove), then SIGTERM, then SIGKILL after a 3 s grace. Quitting the TUI
closes the hosted workspace and stops the runtime the same way, so no background
coding-tools-mcp process outlives ctc.
Telemetry
ctc reports anonymous usage telemetry to its developers: per-tool latency
histograms and success rates, session/approval/merge counters, and crash
fingerprints (crashes go to Sentry). Events are structural metadata only —
never file contents, paths, command text, or tool arguments. Release builds
carry built-in destinations (unset in this repo, so a source build reports
nothing); ctc telemetry off or DO_NOT_TRACK=1 opts out of all reporting.
Locally the model is upload-first: events are buffered under ~/.ctc/telemetry/
only until uploaded, then deleted (3-day cap while offline). ctc telemetry
local on opts into a 30-day local copy so ctc stats [--days N] [--json] (or
/stats in the TUI) can report offline. ctc telemetry status shows the
destination and mode. See docs/telemetry.md for details and
the wire schema.
Development
This is a closed-source beta published as a minified bundle. Build and test locally with the standard scripts:
npm install
npm run build # bundle to dist/ with tsup
npm test # vitest
npm run lint # eslint
npm run typecheck # tsc --noEmitRelease and packaging notes — including the sourcemap leak guard and the npm trusted-publishing flow — live in PUBLISHING.md.
Licensed under the terms in LICENSE.
