Coding Agents MCP

Connect Claude Code, Codex, Cursor, or any MCP client to Plaine so a coding agent can message you when it needs a decision and continue once you reply.

First-release Plaine MCP uses local stdio. The MCP process runs on your machine with an agent API key, so private keys remain outside Plaine for private E2EE conversations.

Hosted HTTP MCP is not the recommended setup. It runs on Plaine’s servers and cannot decrypt private E2EE conversations without Plaine seeing plaintext or key material.

Why Plaine cares about project labels

Plaine creates separate conversations so one coding session does not bleed into another. For coding agents, the natural boundary is usually the local project folder plus the machine running the agent.

Local stdio MCP can read cwd and hostname, so it can derive a stable label like laptop:plane-messaging automatically. That lets Plaine open one conversation per project without asking you to paste conversation ids.

Use local stdio when you want one install to create separate Plaine conversations per local project folder. The Python package has no runtime dependencies and keeps the local process lighter.

Create a pull-mode agent in Plaine first. Open local runtime setup. Local stdio uses that agent’s API key as PLAINE_API_KEY.

Text messaging works in both conversation modes. Attachments are an unreleased private end-to-end encrypted (E2EE) capability, available only on isolated staging where the private E2EE path is enabled. They are not available in normal production MCP conversations, and send_attachment returns an error for an at-rest-only conversation.

For Claude Code:

claude mcp add plaine uvx plaine mcp \
  --env PLAINE_API_KEY=plane_xxx \
  --env PLAINE_API_BASE=https://plaine.chat

For Codex:

[mcp_servers.plaine]
command = "uvx"
args = ["plaine", "mcp"]
env = { PLAINE_API_KEY = "plane_xxx", PLAINE_API_BASE = "https://plaine.chat" }

Generic MCP JSON:

{
  "mcpServers": {
    "plaine": {
      "command": "uvx",
      "args": ["plaine", "mcp"],
      "env": {
        "PLAINE_API_KEY": "plane_xxx",
        "PLAINE_API_BASE": "https://plaine.chat"
      }
    }
  }
}

The Node package remains available:

claude mcp add plaine npx @plaine/mcp \
  --env PLAINE_API_KEY=plane_xxx \
  --env PLAINE_API_BASE=https://plaine.chat

Local stdio auto-labels each conversation from ${hostname}:${basename(cwd)} on first run. Override with PLAINE_CONVERSATION_LABEL=my-label if you want a stable name of your own, or PLAINE_CONVERSATION_ID=conv_xxx to pin to a specific existing conversation.

If two local MCP sessions boot in the same folder, the second one detects the first via a pidfile (${TMPDIR}/plaine-mcp-*.pid) and suffixes its label with its own pid so the two sessions get separate conversations.

Tools

Use send_and_wait when your agent is asking a direct question. It uses the sent message id as the cursor, so older unread messages are not treated as the answer. If your MCP client does not support that tool yet, pass the messageId returned by message_user as since when calling wait_for_message.

Private messages use plaine.message.e2ee.v3 envelopes. Attachments are encrypted locally before upload, and their manifests are carried inside that encrypted message envelope. The local path is never sent to Plaine.

When an inbox message includes attachments, poll_messages, wait_for_message, and send_and_wait return an attachments summary with the attachment ID and encrypted manifest. Pass that attachment ID to download_attachment.

download_attachment requires an explicit absolute destination path and refuses to overwrite an existing file. Choose a new path, for example /Users/me/Downloads/report.pdf; the tool creates it only after the ciphertext download begins and removes a partial file if decryption fails.

Each message can contain up to 10 files, with a combined plaintext limit of 100 MB. Node and Python MCP runtimes do not record voice notes, but send_attachment and download_attachment support audio files through the same encrypted attachment flow. In browser private E2EE conversations on isolated staging, peers can record a voice note for up to five minutes when the browser negotiates audio/webm or audio/mp4; they can preview or remove it before sending.

An audio attachment’s filename, exact MIME type, duration, and plaintext byte size remain encrypted in its manifest. Microphone permission is local to the browser; if it is declined, the peer can attach an audio file instead. Plaine does not transcribe, generate waveforms for, or transcode audio.

Local stdio registers an agent-runtime device through POST /api/agent/devices on the first capability check or message use. The API key identifies the agent; it does not decrypt private conversations. The private runtime key stays on the local machine. describe_message_capabilities() also checks /api/agent/encryption-status so the client can distinguish local key setup, an active runtime-device registration, and whether the encrypted text path is active. A revoked device key cannot be registered again; generate new key material. By default, Python MCP uses the OS secure storage backend exposed by keyring; Node MCP stores a per-agent key file under ~/.config/plaine/agent-runtime-devices/. Set PLAINE_AGENT_DEVICE_KEY_FILE only when you intentionally want to choose the generated runtime private-key file. Python supports that explicit-file backend on macOS and Linux; on Windows, leave it unset to use the OS secure-keyring backend.

Tool approvals arrive through the same polling tools as text replies. If your coding agent sends an approval-requested tool part through Plaine’s API, wait until poll_messages or wait_for_message returns a user message containing data-tool-approval with the matching toolCallId, then resume or skip the tool from that recorded decision.

Node’s key-file backend requires platform support for no-follow file opens and current-user ownership checks. On Windows, use Python with its default secure-keyring backend; Node fails closed rather than weakening key-file protection. Windows execution has not yet been verified for this release.

Config summary: local stdio

Env var Required Default
PLAINE_API_KEY yes —
PLAINE_CONVERSATION_ID no auto-provisioned from label
PLAINE_CONVERSATION_LABEL no ${hostname}:${basename(cwd)}
PLAINE_API_BASE no https://plaine.chat
PLAINE_ON_TIMEOUT no stop
PLAINE_AGENT_DEVICE_KEY_FILE no OS secure storage

Troubleshooting