Skip to main content
Glama

claude-code-mcp

It sounds dumb... but its literally that. Using Claude Code through an MCP server. Its meant for orchestrator agents (like Hermes), giving it fine-grained control over interactive Claude Code sessions running inside tmux. Hermes has a built in Claude Code Skill, but let's be honest, it's huge and takes an enormous amount of the context window...

The core value: a long-running Claude Code REPL stays alive in a tmux pane, and your orchestrator can steer it at any point — send a task without blocking, watch progress, inject a follow-up mid-run, interrupt a wrong turn, or collect the final answer. Way better than using it through claude -p.

The server replaces the whole tmux runbook an orchestrator would otherwise have to carry in its prompt: launch dialogs, readiness gating, idle detection, response extraction. Point Hermes at it and delete the skill file.

Why an orchestrator needs this

Need

Tool

Send a task without blocking on it

session_send

Watch progress while it works

session_tail

Get the final answer back

session_wait

Steer a follow-up into a running session

session_send again

Interrupt a wrong-direction turn

session_interrupt

Answer a question Claude asked

session_respond

One-shot task, structured result

claude_run

Parallel work on one repo

worktree=

Keep quality up on long sessions

session_compact

Related MCP server: agent-mux

The core loop

session_send("work", "implement the auth module", working_dir="/proj")
# → returns immediately: {"dispatched": true, "created": true, ...}

session_tail("work")                 # progress, any time
session_send("work", "use JWT, not sessions")   # steer mid-run
session_wait("work", timeout=300)    # → {"response": "...", "timed_out": false}

session_wait is resumable. If it times out, nothing is lost — the pending dispatch survives and the next call keeps waiting:

r = session_wait("work", timeout=300)
while r["timed_out"]:
    r = session_wait("work", timeout=300)

Why not claude -p?

claude -p exits after one response. There is no channel to inject a follow-up — every turn spawns a fresh process and loses accumulated context.

This server keeps the interactive REPL alive in a tmux pane, so prompts can be injected at any time, history accumulates inside the running process, and mid-run interrupts work as expected.

Print mode is still available via claude_run for one-shot work — and because sessions are minted a --session-id up front, a print-mode call can resume a tmux session's conversation. Start cheap, escalate to interactive.

Features

  • Non-blocking dispatch — session_send returns as soon as the prompt is injected; session_wait collects the result whenever you want it.

  • Steer mid-session — send again while Claude is working. Input is queued and the eventual result still spans the whole run.

  • Launch dialogs handled — workspace trust, the bypass-permissions warning (whose default is "No, exit"), and the effort selector are answered automatically. Anything unrecognised is surfaced as awaiting_input instead of being blind-Entered.

  • Real interrupt — sends Escape, Claude Code's own interrupt, and verifies it took. Ctrl-C is only a fallback (a double Ctrl-C exits the process).

  • Send & auto-create — no separate session_start call required.

  • Shared conversations — a minted --session-id lets print mode and tmux address the same history.

  • Scoped capability — allowed_tools, model, effort, add_dir, append_system_prompt, permission_mode per session.

  • Worktree isolation — worktree="feature-x" for parallel tasks on one repo.

  • Context health — session_context, session_compact, and an optional auto_compact_at threshold on session_wait.

  • Token-efficient — you get the extracted answer, not the pane dump.

  • Pure stdlib + mcp SDK — no heavy dependencies.

Requirements

Tool

Version

Python

≥ 3.11

tmux

≥ 3.4

Claude Code

≥ 2.0

Installation

pip install claude-code-mcp

Or with uv:

uv tool install claude-code-mcp

For development:

git clone https://github.com/joschi655/claude-code-mcp
cd claude-code-mcp
pip install -e ".[dev]"

MCP configuration

Claude Desktop / Claude Code

{
  "mcpServers": {
    "claude-code-mcp": {
      "command": "claude-code-mcp"
    }
  }
}

With uvx (no install required)

{
  "mcpServers": {
    "claude-code-mcp": {
      "command": "uvx",
      "args": ["claude-code-mcp"]
    }
  }
}

Hermes / custom MCP client

{
  "mcpServers": {
    "claude-code-mcp": {
      "command": "python",
      "args": ["-m", "claude_code_mcp"]
    }
  }
}

Tools

session_send(name, prompt, ...)

Send a prompt and return immediately. Creates the session if it does not exist. Sending into a session that is already working is a supported steer.

{ "name": "work", "prompt": "implement feature X", "working_dir": "/proj" }

Returns {dispatched, name, created, steered, state}.

Launch options (applied only when the session is created): working_dir, permission_mode, model, effort, allowed_tools, disallowed_tools, add_dir, append_system_prompt, worktree.


session_wait(name, timeout=300, auto_compact_at=None)

Block until the session finishes, then return the answer. This is the completion trigger — the result comes back as the tool result.

Returns {response, state, timed_out, baseline, elapsed_s, steers}.

  • timed_out: true — still working. Call again; the pending dispatch survives.

  • baseline: "lost" — the server restarted since the prompt was sent, so response is a plain tail of the pane rather than an exact diff.

  • question — present when the session stopped on a prompt.

  • auto_compact_at=70 — run /compact automatically past that context usage.


session_tail(name, lines=40)

Last n lines of pane output, ANSI-stripped. Use this to check on a long task instead of assuming it is stuck.


session_interrupt(name)

Stop the current turn without killing the session. Sends Escape and verifies the session left the busy state; falls back to Ctrl-C only if that fails.


session_respond(name, choice)

Answer a question when state is awaiting_input. choice is an option number ("1", "2") or a key: up, down, enter, escape. Read the question from session_status first.


session_status(name) / session_list() / health()

{
  "name": "work",
  "tmux_alive": true,
  "claude_alive": true,
  "state": "busy",
  "claude_session_id": "02d8d5c3-...",
  "working_dir": "/proj",
  "context_pct": null,
  "pending": true,
  "question": null
}

state is one of missing, starting, busy, awaiting_input, idle.


session_start(name, ...)

Pre-create a session. Usually unnecessary — session_send does it — but useful to fix launch options up front. A fresh session is minted a UUID so claude_run can address the same conversation later.


session_compact(name, focus=None) / session_context(name)

Compress context, or read usage as a percentage. Output quality degrades above roughly 70% context usage. session_context returns null when the TUI output cannot be parsed — treat that as unknown, not zero.


session_stop(name) / session_destroy(name)

session_stop kills the tmux session but keeps the conversation, so it can be resumed later — and so claude_run can address it without transcript contention. session_destroy forgets it entirely.


claude_run(prompt, session_name=None, ...)

One-shot claude -p with structured output:

{
  "result": "...",
  "session_id": "75e2167f-...",
  "num_turns": 3,
  "total_cost_usd": 0.0787,
  "duration_ms": 10276
}

Pass session_name to resume a managed session's conversation. A live busy session is refused — two writers corrupt the transcript. fork=True branches to a new session ID that inherits history, avoiding contention entirely.

Sharing history between print mode and tmux

Sessions get a --session-id at launch, so a conversation is addressable before it produces any output:

session_send("work", "Remember the codeword: BANANA42")
session_wait("work")
session_stop("work")                     # free the transcript

claude_run("What was the codeword?", session_name="work")
# → "BANANA42"

session_start("work")                    # back to interactive, history intact

A running TUI holds its history in memory, so it will not display a print-mode turn until restarted. session_stop first, or use fork=True.

Permissions

Sessions launch with --permission-mode bypassPermissions by default so unattended runs are not blocked waiting for approval. The real safety control is allowed_tools — scope each session to what the task actually needs:

session_send("review", "review the diff vs main",
             working_dir="/proj", allowed_tools=["Read", "Bash(git *)"])

Override per session with permission_mode: acceptEdits, auto, bypassPermissions, manual, dontAsk, plan.

Parallel work

Independent sessions run concurrently. For several tasks against one repo, use worktrees so they don't collide:

session_send("backend",  "fix the auth bug",   working_dir="/proj", worktree="auth-fix")
session_send("frontend", "update the header",  working_dir="/proj", worktree="header")
session_send("tests",    "add API tests",      working_dir="/proj", worktree="api-tests")

health()   # all sessions and their states at a glance

Development

# Unit tests (no tmux/claude required)
pytest tests/test_parser.py tests/test_session_logic.py -v

# Integration tests (requires tmux + claude, spends tokens)
CLAUDE_TMUX_INTEGRATION=1 pytest tests/test_integration.py -v

License

MIT — see LICENSE.

Available Tools

15 tools
claude_runA

Run a one-shot claude -p task and return structured JSON.

Best for work that needs no conversation: review a diff, answer a question, make one contained change. Cheaper and faster than a tmux session.

Pass session_name to resume a managed session's conversation, so one-shot and interactive work share history. A live, busy session is refused (two writers would corrupt the transcript); a live idle one is allowed but the running TUI will not show the turn until restarted. fork=True branches to a new session ID instead, avoiding both issues.

Returns {result, session_id, num_turns, total_cost_usd, duration_ms}. Always set max_turns to bound cost.

Inputs: prompt (required), cwd, fork, model, +9 more.

healthA

System health check: binary availability + summary of all known sessions.

Returns tmux_available / claude_available (are the binaries on PATH), session_count, and sessions (SessionInfo dicts).

Inputs: no named parameters.

session_compactA

Compress the session's context via /compact to recover headroom.

Optionally pass focus to bias what is retained, e.g. "the auth refactor". Output quality degrades above ~70% context usage, so compact before then.

Inputs: name (required), focus.

session_contextA

Run /context and report context usage as a percentage.

Returns {name, context_pct}. context_pct is null when the TUI output cannot be parsed — treat that as unknown, not as zero.

Inputs: name (required).

session_destroyA

Kill the tmux session and remove its stored metadata.

Do this when a task is finished — tmux sessions outlive this server. Do not use it to stop a slow run; use session_interrupt for that.

Inputs: name (required).

session_interruptA

Stop the current turn without killing the session.

Sends Escape (Claude Code's own interrupt), verifies the session left the busy state, and only falls back to Ctrl-C if Escape did not take. Follow up with session_send to redirect.

Inputs: name (required).

session_listA

List all managed sessions (metadata) plus any live tmux sessions.

Inputs: no named parameters.

session_respondA

Answer an on-screen prompt when state is awaiting_input.

choice is an option number ("1", "2", ...) or a navigation key: up, down, enter, escape. Read the question first via session_status.

Launch dialogs (workspace trust, bypass-permissions, effort) are handled automatically — this is only for questions Claude itself asks.

Inputs: name (required), choice (required).

session_sendA

Send prompt to a session and return immediately (non-blocking).

Creates the session if it does not exist — no session_start needed. Call session_wait to collect the answer, or session_tail to watch progress.

Sending while Claude is working is supported and is how you steer a run mid-flight; the input is queued and the pending result still spans the whole run. The returned steered field says which happened.

Launch options apply only when the session is created. Set working_dir on the first send.

Returns {dispatched, name, created, steered, state}.

Inputs: name (required), prompt (required), model, effort, +8 more.

session_set_claude_idA

Bind an existing Claude conversation ID to name for future --resume.

Only needed to adopt a session this server did not create — new sessions are assigned an ID automatically.

Inputs: name (required), claude_session_id (required).

session_startA

Pre-create a Claude Code session. Usually unnecessary — session_send creates one on demand — but useful to fix launch options up front.

  • An existing tmux session called name is returned as-is.

  • A fresh session is minted a UUID (--session-id) so claude_run can address the same conversation later; a stored or supplied claude_session_id triggers --resume instead.

  • permission_mode: one of acceptEdits, auto, bypassPermissions, manual, dontAsk, plan. Defaults to bypassPermissions so unattended runs are not blocked by approval prompts — restrict with allowed_tools instead.

  • worktree: run in an isolated git worktree at .claude/worktrees/<name>.

  • effort: low, medium, high, xhigh, max.

  • Launch options are stored and reused if the session is recreated.

Inputs: name (required), model, effort, add_dir, +7 more.

session_statusA

Return state info for one session.

state is one of: missing, starting, busy, awaiting_input, idle. When it is awaiting_input, question holds the on-screen question and its options. pending means a dispatched prompt has not been collected by session_wait yet. context_pct is null unless /context output happens to be on screen.

Inputs: name (required).

session_stopA

Kill the tmux session but keep the conversation.

Use this to free a session's transcript — so claude_run can address it without contention — while keeping the ability to resume it later with session_start. Use session_destroy to forget it entirely.

Inputs: name (required).

session_tailA

Peek at the last lines of pane output without blocking.

Use this to check on a long-running task instead of assuming it is stuck. Returns {name, state, output}.

Inputs: name (required), lines.

session_waitA

Block until the session finishes its pending work, then return the answer.

This is the completion trigger: it returns Claude's final response as the tool result.

Resumable. If timeout (seconds, max 3600) elapses while Claude is still working, this returns timed_out: true with the partial transcript and keeps the pending dispatch — just call it again. Nothing is lost.

Set auto_compact_at (e.g. 70) to run /compact automatically when context usage crosses that percentage after the turn completes.

Returns {response, state, timed_out, baseline, elapsed_s, steers}. baseline: "lost" means this server restarted since the prompt was sent, so response is a plain tail of the pane rather than an exact diff. A question field appears when the session stopped on a prompt.

Inputs: name (required), timeout, auto_compact_at.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 15 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes, but session_stop and session_destroy both kill the tmux session, differentiated only by keeping metadata. Also, session_status, session_context, and session_tail all read session state, though they target different aspects. Descriptions clarify the boundaries sufficiently.

    Naming Consistency4/5

    The vast majority follow a consistent session_<verb> pattern (list, start, send, wait, tail, respond, interrupt, status, compact, destroy, stop). The claude_run tool deviates by using a different prefix, but it is still a clear, readable name that fits the server's purpose.

    Tool Count4/5

    At 15 tools, it sits at the upper edge of the ideal range. Each tool covers a distinct aspect of session lifecycle or health, so the count feels justified rather than bloated. It is slightly heavy but not excessive for the domain.

    Completeness5/5

    The tool set covers the full session lifecycle: create, send, wait, tail, respond, interrupt, compact, stop, destroy, plus one-shot execution and health/context checks. There are no obvious missing operations that would leave an agent stuck.

    Maintenance

    ActivitySlowing
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      MCP server that manages interactive CLI agent pools using tmux, enabling creation, control, and communication with agents like Claude and Codex.
      7
      MIT
    • A
      license
      B
      quality
      B
      maintenance
      Persistent tmux-backed MCP server that lets Claude Code control Codex CLI's interactive TUI, including slash commands and $imagegen. Run local shells, SSH, Docker, WSL, REPLs, and supported coding agents through durable PTY sessions.
      19
      12,090 npm
      6
      MIT