RFC: MCP Bidirectional Session Channels

Resolved 💬 1 comment Opened Jun 5, 2026 by shjxj Closed Jun 5, 2026

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:

| Workaround | Fatal flaw |
|---|---|
| claude -p headless | New isolated session, no shared context |
| TIOCSTI ioctl | Disabled on Linux ≥ 5.18, requires CAP_SYS_PTRACE |
| Write to /proc/PID/fd/0 | Process never wakes up to read it |
| tmux send-keys | Requires tmux at startup, not zero-friction |
| PTY master fd injection | Requires 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.

| Scenario | Access |
|---|---|
| Same user, same machine | Full access to all session sockets |
| Different user, same machine | No access (Unix socket permissions: 0600) |
| Remote access over network | Not exposed by default; user can forward socket over SSH explicitly |
| session/inject | Requires same-user trust; optionally gated by allow_external_inject: true in ~/.claude/settings.json |
| session/approve | Requires 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

| Proposal | Transport | Scope | This RFC |
|---|---|---|---|
| #24365 claude serve (ACP over network) | TCP/WebSocket | Remote attach, mobile | Overlaps on remote access; this RFC is local-first and MCP-native |
| #53049 External message injection API | Custom HTTP | Prompt injection only | This RFC covers injection + subscription + approval in one coherent protocol |
| PTY injection tools | OS ioctl | Prompt injection only | OS-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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗