Support inbound MCP notifications routed into an active Codex CLI session

Open 💬 14 comments Opened Mar 20, 2026 by jasny
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What feature would you like to see?

I want a way for external channels to push inbound messages into a running Codex CLI session via MCP notifications.

Right now Codex can call MCP tools, but there is no documented path for arbitrary server notifications (for example channel events) to show up as user-visible input in the active CLI thread.

That blocks a practical setup where Telegram or similar channels can message the bot and have those messages land in the existing terminal session.

Why this matters

Claude-style channel plugins can do this pattern by emitting a notification like notifications/claude/channel and the active session receives it as inbound context. In Codex CLI today, the same MCP server can expose tools, but inbound channel messages do not reach the running session.

Using codex app-server is not a replacement for this request. The point is to keep using the normal interactive CLI/TUI session and still accept inbound channel events.

What I expected

A supported mechanism so a configured MCP server can emit an inbound event that Codex CLI maps to a new user message (or equivalent thread item) in the active session.

What I observed

  • MCP tools are available and callable in CLI
  • No obvious way to inject inbound channel notifications into an already running CLI session
  • No command to send input to an active session from another process

Suggested direction

One of these would solve it:

  • A standard notification method Codex CLI listens to and converts into a thread item
  • A local control API/IPC endpoint for "send message to active session"
  • A documented extension point specifically for channel/inbox style MCP servers

Minimal repro context

  • Run Codex CLI in normal interactive mode
  • Connect a custom MCP server that emits async notifications for inbound chat events
  • Confirm tools from that server work
  • Confirm emitted inbound notifications do not appear in the active Codex conversation

This would unlock Telegram/Slack/other inbox integrations without moving to app-server mode.

View original on GitHub ↗

14 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #14722
  • #14164
  • #13543
  • #14110

Powered by Codex Action

sidkandan · 4 months ago

I spent time tracing this more deeply from the perspective of local CLI-to-CLI orchestration, and I think the core need is slightly broader than “support inbound MCP notifications”.

The underlying request is:

please expose an opt-in local ingress for interactive Codex CLI/TUI sessions, so another trusted local process can deliver a real inbound turn (or draft append / turn steer) without PTY emulation.

Why this matters

There is a real gap today between:

  • the programmable Codex surfaces, which already have strong thread/turn semantics via App Server / SDK, and
  • the stock interactive Codex CLI session, which is still hard to integrate with safely from other local tools.

For local orchestrators, wrappers, and tmux-based multi-agent setups, the fallback today is terminal injection:

  • send-keys
  • paste-buffer
  • pipe-pane -I
  • writing directly to the pane PTY

Those all have the same class of problems:

  • text can land in the draft but not submit
  • injected text can race with a human actively typing
  • attribution/dedupe/auditability are ad hoc
  • the automation layer is forced to emulate keystrokes instead of using real session semantics

That is both a UX problem and a safety/integration problem. Local automation ends up relying on brittle PTY tricks instead of a supported control plane.

Why App Server is relevant, but not the whole answer

The docs and code already point in the right architectural direction:

  • thread/start
  • thread/resume
  • thread/fork
  • turn/start
  • turn/steer
  • turn/interrupt

That is exactly the class of primitive local orchestrators want.

The missing piece is: there does not seem to be a supported way to apply those semantics to an already-running interactive Codex CLI/TUI session in local workflows.

So the ask is not “please add a one-off tmux feature”.
It is:

please let interactive sessions opt into a local ingress that reuses the existing thread/turn control model.

What would solve it

Any of these would be strong solutions:

  1. An opt-in local socket/IPC listener for interactive sessions
  • example shape: codex --listen uds:///path/to/session.sock
  • local-only by default
  • uses thread/turn semantics instead of raw terminal input
  1. A local CLI control command for a live interactive session
  • example shape: codex tui send --session <id> --text "..." [--submit]
  • optionally --steer or --interrupt
  1. A documented way to bind an interactive TUI session to App Server semantics
  • so a local controller can attach to the same session/thread and send real turns
  1. If none of the above are desirable, then at least:
  • broader documented notification events for local sidecars (turn-started, turn-interrupted, approval-requested, approval-resolved, idle, draft-presence-changed)
  • so wrappers can make safer delivery decisions without scraping pane output

Why this is useful beyond one niche workflow

This helps more than tmux users.

It would make Codex easier to integrate with:

  • local orchestrators that coordinate multiple coding agents
  • editor/terminal wrappers
  • accessibility tooling
  • remote-control shells
  • background workers that need to hand work back to a human-visible interactive session

The common need is:

“I have an already-running Codex CLI session. I want a trusted local process to hand it structured work without pretending to be a keyboard.”

Suggested product shape

My strong preference would be:

  • keep it opt-in
  • keep it local-only
  • reuse thread/turn semantics already present in App Server / SDK
  • avoid introducing a second unrelated message model just for the TUI

That feels more consistent with the current architecture than adding more PTY-facing hacks or transport-specific exceptions.

If this is directionally aligned with the roadmap, I’d be happy to provide a tighter repro matrix and a more explicit comparison of:

  • PTY injection behavior today
  • desired thread/turn behavior
  • minimal event set needed by local orchestrators
Asuka-wx · 3 months ago

My project solves this with a tmux-based bridge — Telegram messages are forwarded into the active Codex pane. Not via MCP, but it works reliably

I built a Telegram bridge to control Codex from my phone

Turn local Codex sessions on macOS into a Telegram control surface you can actually use from your phone.

https://github.com/Asuka-wx/codex-telegram-bridge

One-line positioning:

Keep your existing ssh + tmux + Codex workflow, and use Telegram only as the remote layer for status, approvals, and follow-up input.

Why It Matters
No workflow rewrite: keep using local tmux + Codex instead of switching to a browser dashboard or remote desktop
Actually remote-usable: not just logs, but approvals, interrupts, and follow-up input
Clear multi-task control: one control topic plus task-specific topics for parallel work
Phone-friendly by design: useful when you are away from your machine but still need to keep work moving
Lower setup friction: one-command installer plus launchd service support

JoeyTeng · 2 months ago

To be complementary to what @Asuka-wx suggests, for Codex desktop you can ask agent to use the new "automation" tool, which allows message delivery to a specific existing thread/session. By that we can achieve at most 1 minutes delay for an inbound message delivery.

Snailflyer · 2 months ago

This thread seems to converge on a broader primitive than MCP notifications: an explicit ingress path into the already-running interactive CLI/TUI session, with delivery semantics that the live session can observe and either accept/reject.

I built Faryo as a narrower tmux-backed version of that idea. It does not solve MCP notification routing, and it is not a Telegram bridge. Codex stays in tmux as the live process; phone/browser sends short input, approvals/interrupts, and handoff back to that same process while showing compact output. The point is to avoid creating a second conversation surface that drifts from the terminal: https://github.com/Snailflyer/faryo

For the use cases here, is the hard requirement "arbitrary MCP server notifications become user messages", or would an authenticated local/mobile control endpoint into the existing CLI session cover most of the workflow?

lostmygithubaccount contributor · 2 months ago

I did this on my fork on this commit and had been using this extensively for inbound messages from iMessage + enabling cross-agent comms for Codex threads (i.e. letting different agents send messages to each other): https://github.com/openai/codex/commit/a614a0612fbf20e1419a956d65cf3f1988cd7ef4

DmitriiSid · 1 month ago

Additional third-party integration context from a desktop voice-agent companion implementation.

We currently support Claude Code and OpenCode with small local integrations:

  • Claude Code: install a plugin under ~/.claude/plugins/...; hooks on Stop, StopFailure, Notification, and PermissionRequest; hook opens a companion reply UI via custom URL and waits on local request/response files.
  • OpenCode: install a global plugin under ~/.config/opencode/plugins/...; listen for session.idle, session.error, and permission.asked; open the same reply UI and send typed response back through the OpenCode SDK when available.

Codex CLI looks close for the outbound/notification half because it has plugins plus hooks. A Codex plugin could likely mirror the Claude Code side by registering hooks and opening a local companion UI.

The blocker is the inbound half for an already-running interactive CLI/TUI session: after the companion captures dictated/typed text, there does not appear to be a supported way to deliver that text back as the next real user turn in the active Codex session. PTY paste would work only as a brittle fallback and loses session semantics.

For this use case, the useful primitive would be an opt-in local ingress into the current interactive session, ideally reusing existing app-server/thread/turn semantics. Concrete examples that would solve it:

  • codex tui send --session <id> --text ... --submit
  • an opt-in Unix socket/local IPC listener exposed by the active TUI session
  • documented binding between a live TUI session and app-server turn/start / turn/steer

This would let voice, accessibility, mobile, and local sidecar tools integrate with Codex CLI without pretending to be a keyboard.

bakemocho · 1 month ago

One related observation from the Codex Desktop / app-server side: this looks
like the same active-session ingress problem from a different surface.

I opened #25914 for the narrower Desktop case, where a local external
app-server client needs a supported way to discover and attach to the currently
active Codex Desktop thread/turn.

What I observed locally:

  • App-server control is usable for threads created by the external client:

start a thread, start a turn, observe events, and interrupt a turn owned by
that client.

  • For an already-running Desktop UI session, the client could see persisted

thread history through thread listing/reading, but did not get a loaded,
controllable target for the active Desktop UI thread.

  • In that state, thread/loaded/list returned no loaded threads from the

client-visible surface, and turn/interrupt against the active-thread
candidate returned thread not found.

So the gap is not only "how do I send text into a terminal". It is also: how can
a trusted local client tell whether a persisted thread is loaded in the current
app-server process, whether it is the interactive thread the user is looking at,
and whether there is an active turn that can be steered or interrupted?

For this issue's broader local-ingress request, it would help if the supported
contract distinguished at least:

  • current interactive/UI thread id and active turn id;
  • persisted but not loaded;
  • loaded in another app-server process;
  • loaded but idle / no active turn;
  • unsupported control of an existing interactive session.

That would let local orchestrators and companion tools use thread/turn
semantics instead of guessing from session files, UI labels, or PTY state.

nullbio · 1 month ago

Bleh. I've spent the last week trying to make an app that allows people to connect Codex CLI TUI's together and support message passing between them, so we can have orchestration agents. But this is the current roadblock I'm running into. We need a way to be able to inject messages into Codex CLI's reliably, from other apps, so that I can send messages telling the agent to check their inbox, etc. Then they can all use my app as a communication bridge. @sidkandan sums it up appropriately. And I've considered tmux, but I don't want to use tmux... I just want to use native Codex CLI, it's a better user experience. Plus I'm using https://github.com/ogulcancelik/herdr which has a great API as well, and so this is the only roadblock at the moment.

Like you said, the goal is to use Codex CLI rather than re-inventing our own TUI or CLI... I'm sick of trying to find alternatives to Codex that work well, but support orchestration. None of them have all the features I need, and the CLI doesn't have any means to accomplish this. Very frustrating.

prassanna-ravishankar · 1 month ago

@nullbio I've kind of solved this in repowire. Uses tmux as a transport so it happens clunkily out of band . A feature like this would be terribly awesome and reduce the dependency surface massively (and bugs)

prassanna-ravishankar · 1 month ago

@nullbio this is exactly the roadblock i hit too. repowire does the connect-multiple-codex-TUIs-and-pass-messages thing today, but via tmux injection out of band, which is why im +1ing this issue. a native ingress would let it drop the hack and the bug surface that comes with it.

if its useful as a reference for the orchestration layer (peer addressing, ask/ack with delivery outcomes, broadcast) happy to point you at the bits that took longest to get right, or compare notes. no point both of us rediscovering the same tmux failure modes.

alexfrmn · 29 days ago

I have a patch that implements the narrow version of this for CLI sessions: opt-in MCP notification ingress into the active session as Submission::UserInput.

Empirical validation on a local patched 0.141.0-line CLI build:

  • A stdio MCP server emitted an inbound notification with toSession: true, msgId, source, and text payload.
  • Codex surfaced it in the running interactive session as a model-visible user turn with an explicit [MCP notification] header.
  • The assistant responded using the existing session context.
  • No tmux/send-keys, PTY emulation, polling wake loop, or side-channel app-server route was involved.
  • Duplicate delivery is guarded by msgId dedupe.

The branch keeps this default-off behind per-server surface_notifications = true, so existing MCP notifications remain trace-only unless the user explicitly trusts that server for session ingress.

Patch link: https://github.com/openai/codex/compare/main...alexfrmn:upstream-pr-mcp-surface-notifications?expand=1

safal207 · 26 days ago

This request maps closely to LS work on typed inbound events, session identity, durable delivery, and authorization boundaries.

A useful distinction is:

MCP notification received
≠ authenticated user instruction
≠ trusted factual claim
≠ authorized side effect

An inbound channel message should become a typed thread item rather than an indistinguishable synthetic user message.

A possible envelope:

{
"schema_version": "mcp-inbound-message/v0.1",
"event_id": "telegram:update:18492",
"source": {
"mcp_server": "telegram-channel",
"channel_type": "telegram",
"channel_id": "chat-882",
"sender_id": "user-431"
},
"target": {
"session_id": "codex-thread-123",
"trajectory_id": "project-alpha"
},
"message": {
"content_type": "text/plain",
"text": "Please check whether the deployment completed"
},
"delivery": {
"deduplication_key": "telegram:update:18492",
"received_at": "2026-06-24T18:42:11Z"
},
"trust": {
"source_authenticated": true,
"sender_verified": false,
"execution_authorized": false
}
}

The receiving path could be:

MCP notification
→ validate configured server
→ authenticate sender/channel
→ validate target session
→ deduplicate
→ append durable thread item
→ notify or wake session
→ classify request
→ normal approval and sandbox flow

The core invariant would be:

«Inbound MCP messages may provide context or request work, but they must not bypass the active session’s approval and sandbox policy.»

This matters especially for messages such as:

“Run this command”
“Deploy production”
“Send this email”
“Delete the generated files”

Delivery should not be equivalent to execution permission.

A second invariant:

«The UI should visibly distinguish local user input from external channel input.»

For example:

[Telegram · unverified sender]
Please deploy the current branch

rather than rendering it exactly like a message typed locally by the authenticated operator.

Useful controls might include:

  • explicit opt-in per MCP server;
  • allowlisted target sessions or trajectories;
  • sender and channel allowlists;
  • maximum message size;
  • rate limiting and queue limits;
  • attachment type restrictions;
  • configurable handling while a turn is running;
  • visible audit history;
  • revocation without restarting Codex.

A durable processing receipt could look like:

{
"event_id": "telegram:update:18492",
"session_id": "codex-thread-123",
"status": "DELIVERED",
"thread_item_id": "item-773",
"turn_started": false,
"action_decision": "REQUIRES_REVIEW"
}

It would also be useful to separate:

DELIVERED
→ DISPLAYED
→ ACKNOWLEDGED
→ PROCESSED
→ ACTION_PROPOSED
→ ACTION_EXECUTED

This prevents the MCP server from treating basic delivery as proof that Codex completed the requested work.

A deterministic conformance fixture could test:

  1. notification from an approved MCP server reaches the selected active session;
  2. message for another session is rejected;
  3. duplicate event ID creates only one thread item;
  4. message arriving mid-turn follows a documented queue policy;
  5. unverified sender is clearly labeled;
  6. inbound command does not bypass approval;
  7. oversized or malformed payload is quarantined;
  8. revoked server can no longer inject messages;
  9. restart preserves pending events without duplication;
  10. every resulting action retains the original inbound event as provenance.

Related LS work:

  • causal audit:

https://github.com/safal207/LS/issues/594

  • authorization and evidence gates:

https://github.com/safal207/LS/issues/595

  • commit-before-effect:

https://github.com/safal207/LS/issues/596

  • deterministic replay:

https://github.com/safal207/LS/issues/597

Would a vendor-neutral "McpInboundMessage" and "InboundMessageReceipt" fixture help test authenticated delivery, session targeting, deduplication, UI attribution, and approval preservation?

safal207 · 26 days ago

This request maps closely to LS work on typed inbound events, session identity, durable delivery, and authorization boundaries.

A useful distinction is:

MCP notification received
≠ authenticated user instruction
≠ trusted factual claim
≠ authorized side effect

An inbound channel message should become a typed thread item rather than an indistinguishable synthetic user message.

A possible envelope:

{
"schema_version": "mcp-inbound-message/v0.1",
"event_id": "telegram:update:18492",
"source": {
"mcp_server": "telegram-channel",
"channel_type": "telegram",
"channel_id": "chat-882",
"sender_id": "user-431"
},
"target": {
"session_id": "codex-thread-123",
"trajectory_id": "project-alpha"
},
"message": {
"content_type": "text/plain",
"text": "Please check whether the deployment completed"
},
"delivery": {
"deduplication_key": "telegram:update:18492",
"received_at": "2026-06-24T18:42:11Z"
},
"trust": {
"source_authenticated": true,
"sender_verified": false,
"execution_authorized": false
}
}

The receiving path could be:

MCP notification
→ validate configured server
→ authenticate sender/channel
→ validate target session
→ deduplicate
→ append durable thread item
→ notify or wake session
→ classify request
→ normal approval and sandbox flow

The core invariant would be:

«Inbound MCP messages may provide context or request work, but they must not bypass the active session’s approval and sandbox policy.»

This matters especially for messages such as:

“Run this command”
“Deploy production”
“Send this email”
“Delete the generated files”

Delivery should not be equivalent to execution permission.

A second invariant:

«The UI should visibly distinguish local user input from external channel input.»

For example:

[Telegram · unverified sender]
Please deploy the current branch

rather than rendering it exactly like a message typed locally by the authenticated operator.

Useful controls might include:

  • explicit opt-in per MCP server;
  • allowlisted target sessions or trajectories;
  • sender and channel allowlists;
  • maximum message size;
  • rate limiting and queue limits;
  • attachment type restrictions;
  • configurable handling while a turn is running;
  • visible audit history;
  • revocation without restarting Codex.

A durable processing receipt could look like:

{
"event_id": "telegram:update:18492",
"session_id": "codex-thread-123",
"status": "DELIVERED",
"thread_item_id": "item-773",
"turn_started": false,
"action_decision": "REQUIRES_REVIEW"
}

It would also be useful to separate:

DELIVERED
→ DISPLAYED
→ ACKNOWLEDGED
→ PROCESSED
→ ACTION_PROPOSED
→ ACTION_EXECUTED

This prevents the MCP server from treating basic delivery as proof that Codex completed the requested work.

A deterministic conformance fixture could test:

  1. notification from an approved MCP server reaches the selected active session;
  2. message for another session is rejected;
  3. duplicate event ID creates only one thread item;
  4. message arriving mid-turn follows a documented queue policy;
  5. unverified sender is clearly labeled;
  6. inbound command does not bypass approval;
  7. oversized or malformed payload is quarantined;
  8. revoked server can no longer inject messages;
  9. restart preserves pending events without duplication;
  10. every resulting action retains the original inbound event as provenance.

Related LS work:

  • causal audit:

https://github.com/safal207/LS/issues/594

  • authorization and evidence gates:

https://github.com/safal207/LS/issues/595

  • commit-before-effect:

https://github.com/safal207/LS/issues/596

  • deterministic replay:

https://github.com/safal207/LS/issues/597

Would a vendor-neutral "McpInboundMessage" and "InboundMessageReceipt" fixture help test authenticated delivery, session targeting, deduplication, UI attribution, and approval preservation?