claude-code - 💡(How to fix) Fix RFC: MCP Bidirectional Session Channels

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

$ claude session inject --latest "explain this error"

Root Cause

2.3 The ecosystem is fragmented because of this gap

Fix Action

Fix / Workaround

This is a protocol-level change, not a PTY hack or OS-specific workaround. It turns every running Claude Code session into a first-class MCP node on the local machine — addressable, composable, and interoperable with the entire MCP ecosystem.

2.2 The workarounds are all broken

Every existing workaround requires OS-level privilege escalation, breaks session continuity, or is platform-specific:

Code Example

Claude Code (MCP Client)  →→→  MCP Server (external tool)

---

~/.claude/sessions/<session-id>.sock   # Unix domain socket

---

~/.claude/sessions/index.json
{
  "sessions": [
    {
      "id": "sess_abc123",
      "socket": "~/.claude/sessions/sess_abc123.sock",
      "cwd": "/home/user/myproject",
      "started_at": "2025-06-01T10:00:00Z",
      "status": "idle"  // idle | thinking | waiting_approval
    }
  ]
}

---

// External tool → Claude Code
{
  "jsonrpc": "2.0",
  "method": "session/subscribe",
  "params": {
    "events": ["message", "tool_call", "tool_result", "approval_request", "status"]
  }
}

// Claude Code → External tool (streaming events)
{
  "jsonrpc": "2.0",
  "method": "session/event",
  "params": {
    "type": "message",
    "role": "assistant",
    "content": "I'll start by reading the directory structure...",
    "session_id": "sess_abc123",
    "timestamp": "2025-06-01T10:01:23Z"
  }
}

{
  "jsonrpc": "2.0",
  "method": "session/event",
  "params": {
    "type": "approval_request",
    "tool": "bash",
    "command": "rm -rf ./dist",
    "session_id": "sess_abc123"
  }
}

---

// External tool → Claude Code
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "session/inject",
  "params": {
    "prompt": "Now explain the auth module",
    "source": "vscode-extension",   // optional: for audit logging
    "mode": "append"                // append | interrupt
  }
}

// Claude Code → External tool (acknowledgement)
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "accepted": true,
    "queued_at": "2025-06-01T10:01:30Z"
  }
}

---

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "session/approve",
  "params": {
    "request_id": "approval_789",
    "decision": "allow",           // allow | deny | allow_always
    "session_id": "sess_abc123"
  }
}

---

# List all running sessions
$ claude session list
ID            CWD                    STATUS    STARTED
sess_abc123   ~/projects/myapp       idle      10 mins ago
sess_def456   ~/projects/api         thinking  2 mins ago

# Send a prompt to a specific session
$ claude session inject sess_abc123 "run the test suite"

# Send to the most recently active session
$ claude session inject --latest "explain this error"

# Watch a session's output stream
$ claude session watch sess_abc123

# Approve a pending request
$ claude session approve sess_abc123

---

User selects code in VS Code
Extension calls session/inject: "refactor this function for readability: <code>"
Claude Code acts on it in the existing terminal session

---

Orchestrator: "The API agent needs you to implement the /users endpoint"
→ session/inject into the Claude Code session with the full project open
→ session/subscribe to watch progress and collect the result
Orchestrator continues with the implementation

---

Frontend session: "I need a new API endpoint: POST /api/upload"
→ session/inject into backend session
→ backend session implements it
→ frontend session subscribes and continues once done

---

{
  "capabilities": {
    "session": {
      "subscribe": true,
      "inject": true,
      "approve": true,
      "events": ["message", "tool_call", "tool_result", "approval_request", "status"]
    }
  }
}
RAW_BUFFERClick to expand / collapse

RFC: MCP Bidirectional Session Channels

Enabling External Tools to Subscribe and Inject into Live Claude Code Sessions

Status: Proposal
Target: Anthropic MCP Protocol Team / Claude Code Team
Repo: https://github.com/anthropics/claude-code
Related Issues: #24365, #53049


1. Summary

This RFC proposes extending the MCP protocol with a bidirectional session channel primitive. When Claude Code starts an interactive session, it exposes a named MCP endpoint. Any MCP-compatible external tool can:

  • Subscribe to the session's output stream (read what Claude is doing)
  • Inject a prompt into the session (write to Claude as if the user typed it)

This is a protocol-level change, not a PTY hack or OS-specific workaround. It turns every running Claude Code session into a first-class MCP node on the local machine — addressable, composable, and interoperable with the entire MCP ecosystem.


2. Problem Statement

2.1 Current MCP communication is unidirectional

Today MCP only supports one direction:

Claude Code (MCP Client)  →→→  MCP Server (external tool)

Claude calls tools on external servers. External servers cannot initiate communication back to a running Claude Code session. There is no standardized way for an external process to:

  • Push a prompt into an active session
  • Observe session output as a stream
  • React to Claude's tool calls or approval requests

2.2 The workarounds are all broken

Every existing workaround requires OS-level privilege escalation, breaks session continuity, or is platform-specific:

WorkaroundFatal flaw
claude -p headlessNew isolated session, no shared context
TIOCSTI ioctlDisabled on Linux ≥ 5.18, requires CAP_SYS_PTRACE
Write to /proc/PID/fd/0Process never wakes up to read it
tmux send-keysRequires tmux at startup, not zero-friction
PTY master fd injectionRequires pidfd_getfd, platform-specific, fragile

None of these belong in a production ecosystem. They are workarounds for a missing protocol primitive.

2.3 The ecosystem is fragmented because of this gap

20+ third-party projects (mobile apps, web UIs, IDE bridges, voice assistants) have each independently built fragile PTY scrapers and custom relay infrastructure to work around this limitation. This is the same fragmentation that existed before LSP standardized editor ↔ language server communication. The solution is the same: standardize the protocol.


3. Proposed Solution: MCP Session Channels

3.1 Core concept

When Claude Code starts, it registers itself as an MCP Session Node at a well-known local endpoint:

~/.claude/sessions/<session-id>.sock   # Unix domain socket

A global index file lists all active sessions:

~/.claude/sessions/index.json
{
  "sessions": [
    {
      "id": "sess_abc123",
      "socket": "~/.claude/sessions/sess_abc123.sock",
      "cwd": "/home/user/myproject",
      "started_at": "2025-06-01T10:00:00Z",
      "status": "idle"  // idle | thinking | waiting_approval
    }
  ]
}

Any same-user process can connect to these sockets using standard MCP message framing.

3.2 Two new MCP capabilities

Capability 1: session/subscribe — output stream

External tools subscribe to everything Claude produces in a session.

// External tool → Claude Code
{
  "jsonrpc": "2.0",
  "method": "session/subscribe",
  "params": {
    "events": ["message", "tool_call", "tool_result", "approval_request", "status"]
  }
}

// Claude Code → External tool (streaming events)
{
  "jsonrpc": "2.0",
  "method": "session/event",
  "params": {
    "type": "message",
    "role": "assistant",
    "content": "I'll start by reading the directory structure...",
    "session_id": "sess_abc123",
    "timestamp": "2025-06-01T10:01:23Z"
  }
}

{
  "jsonrpc": "2.0",
  "method": "session/event",
  "params": {
    "type": "approval_request",
    "tool": "bash",
    "command": "rm -rf ./dist",
    "session_id": "sess_abc123"
  }
}

Capability 2: session/inject — prompt injection

External tools push a prompt into the session as if the user typed it.

// External tool → Claude Code
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "session/inject",
  "params": {
    "prompt": "Now explain the auth module",
    "source": "vscode-extension",   // optional: for audit logging
    "mode": "append"                // append | interrupt
  }
}

// Claude Code → External tool (acknowledgement)
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "accepted": true,
    "queued_at": "2025-06-01T10:01:30Z"
  }
}

mode: "append" queues the prompt for after the current task completes.
mode: "interrupt" is equivalent to Ctrl+C then sending the prompt (requires user to have enabled allow_interrupt in settings).

Capability 3: session/approve — approval responses

External tools can respond to approval requests without the user being at the terminal.

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "session/approve",
  "params": {
    "request_id": "approval_789",
    "decision": "allow",           // allow | deny | allow_always
    "session_id": "sess_abc123"
  }
}

3.3 Session discovery CLI

# List all running sessions
$ claude session list
ID            CWD                    STATUS    STARTED
sess_abc123   ~/projects/myapp       idle      10 mins ago
sess_def456   ~/projects/api         thinking  2 mins ago

# Send a prompt to a specific session
$ claude session inject sess_abc123 "run the test suite"

# Send to the most recently active session
$ claude session inject --latest "explain this error"

# Watch a session's output stream
$ claude session watch sess_abc123

# Approve a pending request
$ claude session approve sess_abc123

4. What This Unlocks for the MCP Ecosystem

This single protocol addition turns Claude Code sessions into composable building blocks. Here is what becomes possible without any further changes to Claude Code:

IDE plugins

A VS Code extension selects a block of code and calls session/inject with the selected text as context. No PTY, no subprocess, no OS hacks — one MCP call.

User selects code in VS Code
→ Extension calls session/inject: "refactor this function for readability: <code>"
→ Claude Code acts on it in the existing terminal session

Multi-agent orchestration

An orchestrator agent (running as a separate claude -p process) delegates subtasks to a Claude Code session that has full project context loaded:

Orchestrator: "The API agent needs you to implement the /users endpoint"
→ session/inject into the Claude Code session with the full project open
→ session/subscribe to watch progress and collect the result
→ Orchestrator continues with the implementation

Mobile companion app

A mobile app subscribes to session/subscribe over an SSH tunnel, shows streaming output, and can respond to approval_request events with a single tap — without staring at raw terminal output.

Voice assistant integration

A local speech-to-text daemon transcribes speech and calls session/inject. Claude Code responds in the terminal. The daemon subscribes to output and reads responses aloud via TTS.

Automated testing pipeline

A CI script starts Claude Code in a project directory, injects a series of prompts, subscribes to the output stream, and collects structured results — all through MCP, with full session context preserved between prompts.

Cross-session coordination

One Claude Code session working on the frontend can inject a prompt into another session working on the backend:

Frontend session: "I need a new API endpoint: POST /api/upload"
→ session/inject into backend session
→ backend session implements it
→ frontend session subscribes and continues once done

5. Security Model

The security boundary is Unix user identity, which is already the trust boundary for Claude Code itself.

ScenarioAccess
Same user, same machineFull access to all session sockets
Different user, same machineNo access (Unix socket permissions: 0600)
Remote access over networkNot exposed by default; user can forward socket over SSH explicitly
session/injectRequires same-user trust; optionally gated by allow_external_inject: true in ~/.claude/settings.json
session/approveRequires allow_external_approve: true in settings (off by default)

No new attack surface is introduced that doesn't already exist at the OS level. A process that can write to your Unix socket can already read your files and run commands as you.


6. Protocol Extension Specification

6.1 Capability advertisement

Claude Code announces support in the MCP initialize response:

{
  "capabilities": {
    "session": {
      "subscribe": true,
      "inject": true,
      "approve": true,
      "events": ["message", "tool_call", "tool_result", "approval_request", "status"]
    }
  }
}

6.2 Transport

Unix domain socket, using existing MCP JSON-RPC 2.0 framing. No new transport is needed. For remote access, SSH port forwarding of the socket path is the user's responsibility — Claude Code does not need to expose a network port.

6.3 Backward compatibility

This is a purely additive extension. Existing MCP servers and clients that do not implement session/* methods are unaffected. Claude Code continues to work exactly as before when no external client connects to the session socket.


7. Comparison with Existing Proposals

ProposalTransportScopeThis RFC
#24365 claude serve (ACP over network)TCP/WebSocketRemote attach, mobileOverlaps on remote access; this RFC is local-first and MCP-native
#53049 External message injection APICustom HTTPPrompt injection onlyThis RFC covers injection + subscription + approval in one coherent protocol
PTY injection toolsOS ioctlPrompt injection onlyOS-specific, fragile, no output subscription

This RFC is distinguished by: using MCP as the protocol rather than inventing a new one, and covering the full bidirectional surface (inject + subscribe + approve) as a unified capability.


8. Implementation Sketch

From Claude Code's perspective, the implementation is minimal:

  1. On session start, create ~/.claude/sessions/<id>.sock and write to index.json
  2. Accept MCP connections on the socket (same JSON-RPC handler already used for subprocess MCP)
  3. For session/subscribe: pipe internal event emitter to the connected client
  4. For session/inject: push the prompt string into the existing input queue (same code path as keyboard input)
  5. For session/approve: resolve the pending approval promise
  6. On session end, remove the socket file and update index.json

The core event emitter and input queue already exist inside Claude Code. This is plumbing work, not an architectural change.


9. Ask

  1. Add session/subscribe, session/inject, and session/approve to the MCP specification as an optional capability set under the namespace session/*
  2. Implement this capability in Claude Code, gated behind a settings flag initially (experimental_session_ipc: true)
  3. Publish the socket path convention (~/.claude/sessions/) so third-party MCP servers can build on it immediately

This does not require a new protocol. It does not require network exposure. It does not require OS privileges. It requires Claude Code to listen on a Unix socket it already knows how to create — and route two new message types through event machinery it already has.

The MCP ecosystem's long-term value depends on Claude being a composable node, not just a consumer. This is the missing half.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING