Read, search, triage, and send iMessages from local ChatGPT Work and Codex sessions in the ChatGPT macOS app through a permission-aware helper on your Mac.
This is an independent open-source project by Jeff Huber. It is not made, endorsed, or supported by Apple or OpenAI.
Security: Report vulnerabilities privately as described in SECURITY.md.
| Surface | Support | Why |
|---|---|---|
| ChatGPT Work with Work locally selected | Supported | The plugin's STDIO MCP server runs on the Mac. |
| Codex in the ChatGPT macOS app | Supported | Codex loads the same local plugin and MCP configuration. |
| Codex CLI or IDE extension on the same Mac | Expected | Local Codex clients share MCP configuration, but the desktop app is the primary tested surface. |
| ChatGPT web, hosted Chat, cloud Work, mobile, or remote Codex | Not supported | Cloud sessions cannot reach a private process or Messages database on your Mac. |
Local Work, Codex, and plugin availability can vary by ChatGPT plan, workspace policy, role, and staged rollout. See OpenAI's plugin documentation for the current product requirements.
There is no cloud relay, hosted MCP endpoint, account service, or telemetry. The helper and MCP server make no outbound network requests. However, message content returned to ChatGPT or Codex becomes model input and is processed by OpenAI under the data controls for your account and workspace.
- Review recent conversations and surface likely replies
- Search allowed message history by substring
- Retrieve one conversation's recent history
- Calculate response-time statistics
- Resolve Contacts entries before a send
- Send a plain-text iMessage or SMS after two confirmations
The plugin does not support attachments, reactions, edits, deletes, group sends, or arbitrary database queries.
- macOS 13 or newer
- ChatGPT macOS app with local Work or Codex available
- Xcode Command Line Tools:
xcode-select --install - Python 3.10 or newer
- Full Disk Access for the compiled helper wrapper
- Automation access to Messages for sending
- Network access during installation to install the pinned official Python MCP
SDK (
mcp==2.0.0) into a dedicated local virtual environment
The installers select two Python roles independently. The FDA helper uses
Python 3.9 or newer with the dir_fd support required by its no-follow file
operations; the local MCP environment uses Python 3.10 or newer. To override
them, scope absolute paths to one install:
IMESSAGE_HELPER_PYTHON=/usr/bin/python3 \
IMESSAGE_PYTHON=/opt/homebrew/bin/python3.12 \
./install-hardened.shAn explicitly set empty, missing, or unsupported override fails closed. Hardened mode additionally requires the helper interpreter and every parent directory to be root-owned, free of symlinks, and not group/world-writable. The MCP interpreter and resulting virtual environment remain user-owned and do not receive Full Disk Access. Avoid exporting these shared override names globally when using sibling iMessage helpers.
The helper protocol is independently versioned and currently reports 1.1.
For a reproducible install, download every asset from the latest release, then verify them:
shasum -a 256 -c SHA256SUMSUnpack the verified archive and enter its directory. Release archives contain
source only; the macOS binaries are compiled and signed locally. To contribute
or follow main instead, clone the repository:
git clone https://github.com/jeffhuber/chatgpt-codex-imessage-plugin.git
cd chatgpt-codex-imessage-pluginChoose deliberately based on the local threat model:
| Mode | Best fit | Security and operational tradeoff |
|---|---|---|
| Standard | Default. Personal Mac, getting started, and everyday use. | No sudo; user-writable code does not resist a compromised same-user process. |
| Hardened | You run other unsandboxed automation as your user, or want a root-owned default-deny allowlist. | Root-owned validated code; requires sudo and explicit allowlist maintenance. |
Standard per-user install:
You can install in two ways: directly from the git checkout (traditional), or by copying to a dedicated live folder (recommended for production use alongside sibling helpers like Grok Bot or Claude Cowork).
Option 1: Live install (recommended for coexistence)
Copy the repository to ~/imessage-bridge-chatgpt and run the installer there:
rsync -a --exclude=.git ./ ~/imessage-bridge-chatgpt/
cd ~/imessage-bridge-chatgpt
./install.shThe installer detects that it's not running from a git checkout and keeps the
wrapper, control queue, contacts, and MCP virtual environment together in
~/imessage-bridge-chatgpt. This matches the Grok Bot and Claude Cowork install
pattern. The git checkout at ~/src/chatgpt-codex-imessage-plugin remains your
source workspace; updates are applied by copying changed files to the live
folder and re-running ./install.sh there.
Option 2: Git checkout install
./install.shThe standard installer keeps executable helper code in the clone and stores
runtime state in ~/Library/Application Support/ChatGPTCodexIMessage. It needs
no administrator access, but another unsandboxed process running as your user
could replace that code. Its default blocklist protects against accidental
disclosure, not a compromised same-user process.
You can override the bridge location with the CHATGPT_CODEX_IMESSAGE_BRIDGE
environment variable if it contains a non-empty absolute path without control
characters. Empty, relative, or control-character values fail closed.
Hardened install:
./install-hardened.shThe hardened installer uses sudo narrowly to place trusted helper code under:
/Library/Application Support/ChatGPTCodexIMessage/users/<uid>/libexec
Runtime requests, responses, logs, nonces, and the MCP virtual environment stay user-owned under:
~/Library/Application Support/ChatGPTCodexIMessage
Reads default to deny. Add each phone number, email, or group identifier that the helper may return:
CODE_ROOT="/Library/Application Support/ChatGPTCodexIMessage/users/$UID/libexec"
python3 "$CODE_ROOT/tools/configure_allowlist.py" add +15551234567The allowlist is root-owned. A same-user process can submit read requests but cannot broaden the set of conversations the helper may return.
Both installers:
- Build and locally code-sign an FDA wrapper and native confirmation app.
- Install the independent
com.jeffhuber.chatgpt-codex-imessageLaunchAgent. - Create private mode-700 runtime directories.
- Install the pinned MCP SDK in the bridge's
mcp-venv. - Copy the plugin to
~/plugins/chatgpt-codex-imessage-plugin. - Add it to the Personal plugin marketplace and attempt to enable it.
Set INSTALL_OPENAI_PLUGIN=0 to install only the helper. Run
./install-plugin.sh later to install the local plugin and MCP runtime.
After installation, grant Full Disk Access to the exact wrapper path printed by the installer:
- Hardened:
/Library/Application Support/ChatGPTCodexIMessage/users/<uid>/libexec/bin/chatgpt-codex-imessage-helper - Standard:
<live folder or clone>/bin/chatgpt-codex-imessage-helper
Open System Settings > Privacy & Security > Full Disk Access, use the +
button, press Cmd-Shift-G, and enter the path.
The first real send prompts for Automation > Messages access. The native confirmation window appears only after that permission is available.
Restart the ChatGPT desktop app after installing or updating the plugin. In the Plugins directory, confirm iMessage for ChatGPT and Codex is installed from the Personal source if automatic enablement was unavailable.
Keep the composer set to Work locally, or open Codex in the macOS app, and try:
- "Review my iMessages from the last day and flag replies I owe."
- "Search my iMessages for dinner plans from the last month."
- "Show my conversation with Alex from this week."
- "Text alex@example.com: Running ten minutes late."
The plugin calls imessage_status before message access. Hardened installs with
an empty allowlist intentionally return no messages or contacts.
Every send has two enforced gates:
preview_imessagevalidates the individual phone/email recipient and full body, checks policy, and creates a single-use 60-second nonce bound to the exact(recipient, text, service)tuple. The assistant must show that complete preview and wait for approval in the conversation.send_imessageconsumes the nonce and displays a native AppKit window with the resolved name, exact address, service, and complete body. Cancel is the keyboard default. The helper sends only after the user deliberately clicks Send.
Native send confirmation dialog showing the recipient, service, and full message text. Cancel is the keyboard default. This is the same NSAlert dialog family used by the Claude Cowork and Grok Bot helpers.
The nonce prevents blind, replayed, and payload-swapped sends. It does not authorize a same-user process that can read and write the bridge. The native dialog is the final send authorization boundary. Cancel any unexpected dialog.
The MCP send tool is marked as a non-idempotent external write. A timeout warns that delivery may be unknown and must never be retried automatically. The bundled MCP configuration prompts for write tools by default; the native dialog still remains mandatory even if the host-side tool call is approved.
ChatGPT Work locally / Codex on this Mac
|
local STDIO MCP server
(narrow typed tools)
|
mode-700 JSON file bridge
|
independent LaunchAgent wrapper
(Full Disk Access holder)
|
chat.db and Messages.app
The MCP process does not receive Full Disk Access or Automation permission. It can only submit protocol requests to the bridge. The locally signed wrapper is the TCC identity, validates its loaded components, applies read policy, and owns the final send confirmation.
Responses are mode 600, read with O_NOFOLLOW, bounded to 16 MiB by the MCP
client, and deleted immediately after parsing. The helper independently reaps
abandoned responses after one hour.
| Tool | Effect |
|---|---|
imessage_status |
Compatibility and installation checks; reads no messages |
review_imessages |
Triage recent allowed threads |
search_imessages |
Search allowed messages |
get_imessage_history |
Retrieve one allowed conversation |
get_imessage_response_stats |
Calculate response timing |
lookup_imessage_contacts |
Find allowed contact handles |
preview_imessage |
Validate payload and create an expiring nonce |
send_imessage |
External write requiring nonce and native confirmation |
There is deliberately no arbitrary SQL, filesystem, AppleScript, or generic "run action" tool.
These are three independently deployed helpers with a security-critical source
core kept in parity. Each installed host has its own isolated runtime state and
authorization identity; those boundaries must not be unified. The shared source
contract is recorded in shared-core.json and enforced by CI. See
Shared Core Maintenance.
- Grok Bot — LaunchAgent
com.jeffhuber.grokbot-imessage, wrappergrokbot-imessage-helper— https://github.com/jeffhuber/grokbot-imessage-skill - Claude Cowork — LaunchAgent
com.jeffhuber.claudecowork-imessage, wrapperclaude-cowork-imessage-helper— https://github.com/jeffhuber/claudecowork-imessage-skill - ChatGPT/Codex — LaunchAgent
com.jeffhuber.chatgpt-codex-imessage, wrapperchatgpt-codex-imessage-helper— https://github.com/jeffhuber/chatgpt-codex-imessage-plugin
All three can be installed and loaded at once on the same Mac:
| Host | LaunchAgent | Default hardened product root |
|---|---|---|
| ChatGPT/Codex | com.jeffhuber.chatgpt-codex-imessage |
/Library/Application Support/ChatGPTCodexIMessage |
| Claude Cowork | com.jeffhuber.claudecowork-imessage |
/Library/Application Support/ClaudeCoworkIMessage |
| Grok Bot | com.jeffhuber.grokbot-imessage |
/Library/Application Support/GrokBotIMessage |
They do not share wrappers, LaunchAgents, bridge queues, policies, logs, responses, nonces, or TCC identities. They do share the system Messages database and Messages.app Automation surface. Do not configure two hosts to use the same bridge directory.
Full Disk Access is much broader than Messages access. A compromised FDA helper could read other protected user files. The hardened install reduces code replacement risk but does not protect against root compromise, malicious administrator action, compromise of OpenAI or Apple software, or disclosure of content intentionally returned to the active model conversation.
Messages are two-sided. Other participants have not necessarily consented to LLM processing. Allowlist only the conversations appropriate for your use.
Standard installs use contacts/read_policy.txt (blocklist by default).
Add one phone number, email, or group ID per line to
contacts/blocked_chats.txt; blocked threads are removed before response JSON
is written. You may set the policy to allowlist and populate
contacts/allowed_chats.txt, but both files remain user-editable in standard
mode. The blocklist always takes precedence.
The helper masks verification codes in recognized contexts, card-like digit runs, and US SSNs. This regex redaction is best-effort, not a DLP boundary: context-free codes and PINs, API keys, bank or routing numbers, addresses, dates of birth, and alternative separators can pass through. Treat the blocklist or hardened allowlist as the primary disclosure boundary.
Read SECURITY.md before installing. Protocol details are in docs/PROTOCOL.md, and the post-install checklist is in docs/SMOKE_TEST.md. Release notes and version history are in CHANGELOG.md.
For a standard install:
python3 tools/doctor.py \
--bridge "$HOME/Library/Application Support/ChatGPTCodexIMessage" \
--code-root "$PWD" \
--architecture standardFor a hardened install, use the exact doctor command printed by the installer.
The doctor can only test whether its own shell process reads chat.db; use
the smoke test as the authoritative check of the wrapper's Full Disk Access.
Developer checks:
source tools/select_python.sh
MCP_PYTHON="$(find_mcp_python "$PATH")"
"$MCP_PYTHON" -m venv .venv
.venv/bin/python -m pip install -r requirements-mcp.txt
IMESSAGE_TEST_PYTHON="$PWD/.venv/bin/python" ./tools/test.sh
bash -n install.sh install-hardened.sh install-plugin.sh \
uninstall.sh uninstall-hardened.sh scripts/run-mcp-server.sh \
tools/bridge_paths.sh tools/select_python.sh tools/test.sh
shellcheck install.sh install-hardened.sh install-plugin.sh \
uninstall.sh uninstall-hardened.sh scripts/run-mcp-server.sh \
tools/bridge_paths.sh tools/select_python.sh tools/test.sh
python3 /path/to/plugin-creator/scripts/validate_plugin.py .tools/test.sh validates and prints the selected interpreter before any test
runs. The repository launcher is intentionally installed-plugin-only; run
./install-plugin.sh before testing it through ChatGPT. CI also compiles the C
wrapper and native AppKit confirmation helper on macOS.
Download and verify the new release assets, review CHANGELOG.md, then copy the
source over the same live folder or update the same clone. Rerun the same
installer mode you previously used; this refreshes the helper, local plugin,
MCP environment, and LaunchAgent. Restart ChatGPT, run the installer-printed
doctor command, and complete the smoke test. Regrant Full
Disk Access only if macOS no longer recognizes the rebuilt wrapper; the
installer prints its exact path.
Use the matching uninstaller:
./uninstall.sh
# or
./uninstall-hardened.shThe uninstaller removes the LaunchAgent, local plugin, marketplace entry, and
MCP virtual environment. Message-related runtime files remain for review.
Delete ~/Library/Application Support/ChatGPTCodexIMessage only when ready,
then revoke the wrapper under Full Disk Access and Automation in System
Settings.
MIT. See LICENSE.
