clients.md


title: "Client setup" description: "Connect ChatGPT, Claude, Cursor, VS Code, Codex, Gemini CLI and other MCP clients to the hosted MachineMail MCP server, signing in with OAuth or an API key." canonical: "https://machinemail.dev/docs/agents/clients"


Client setup

Connect ChatGPT, Claude, Cursor, VS Code, Codex, Gemini CLI and other MCP clients to the hosted MachineMail MCP server, signing in with OAuth or an API key.

Source: https://machinemail.dev/docs/agents/clients

Every client connects to the same endpoint:

URL:  https://mcp.machinemail.dev/mcp

There are two ways to authenticate:

Supported clients

Every client below is tested against MachineMail by replaying its own sign-in sequence: discovery, client identification, authorization, token exchange, initialize and tools/list, refresh and sign-out.

Client How it identifies itself Token endpoint auth Setup
ChatGPT (plugins, including dots) Client metadata document chatgpt.com/oauth/client.json none (PKCE) ChatGPT
ChatGPT, when the app builder picks DCR Dynamic registration client_secret_post or client_secret_basic ChatGPT
claude.ai, Claude Desktop, mobile, Cowork Client metadata document claude.ai/oauth/mcp-oauth-client-metadata none (PKCE) claude.ai and Claude Desktop
Claude Code Client metadata document claude.ai/oauth/claude-code-client-metadata none, or a secret with --client-secret Claude Code
VS Code (GitHub Copilot) Client metadata document vscode.dev/oauth/client-metadata.json, else dynamic registration none VS Code
Cursor Dynamic registration none, or a static client ID and secret Cursor
Codex CLI Client metadata document chatgpt.com/oauth/codex/client.json none (PKCE) Codex
Gemini CLI Dynamic registration none (PKCE) Gemini CLI
Zed Client metadata document zed.dev/oauth/client-metadata.json none (PKCE) Zed
Windsurf Dynamic registration none (PKCE) Windsurf
Goose Client metadata document goose-docs.ai/oauth/client-metadata.json none (PKCE) Goose
JetBrains IDEs Dynamic registration (Air), or mcp-remote, or an API key none (PKCE) JetBrains IDEs
OpenCode, MCP Inspector, mcp-remote Dynamic registration none (PKCE) OpenCode, Other clients
OpenAI Responses API, Claude Messages API No sign-in: you pass a credential n/a Model APIs

Each section starts with OAuth. Where it shows an API key, it reads the key from an environment variable, MACHINEMAIL_API_KEY in the examples, so it stays out of files you commit:

export MACHINEMAIL_API_KEY=mm_live_...

Note: Apps you connect with OAuth are listed under Settings → Connected apps in the dashboard, where you can disconnect them. Don't put auth.md access tokens in client configs: they expire after an hour and clients don't renew them.

Claude Code

Add the server with the CLI, without a header:

claude mcp add --transport http --scope user machinemail https://mcp.machinemail.dev/mcp

Then run /mcp in a Claude Code session, select machinemail and choose Authenticate. Your browser opens on the Connect page. After you approve, Claude Code stores the tokens and refreshes them when they expire. Claude Code identifies itself with a client metadata document, so the Connect page shows it as Claude Code from claude.ai.

--scope user makes the server available in every project. Leave it out to add it to the current project only, or use --scope project to write it to the project's .mcp.json for your team. With OAuth, each person signs in with their own account, so the shared file holds no secret:

{
  "mcpServers": {
    "machinemail": {
      "type": "http",
      "url": "https://mcp.machinemail.dev/mcp"
    }
  }
}

To use an API key instead, for example in CI, add the header:

claude mcp add --transport http --scope user machinemail https://mcp.machinemail.dev/mcp \
  --header "Authorization: Bearer $MACHINEMAIL_API_KEY"

In a shared .mcp.json, reference the variable instead of the value ("headers": { "Authorization": "Bearer ${MACHINEMAIL_API_KEY}" }), and each person's environment supplies their own key.

Check it with claude mcp list, or /mcp inside a session.

ChatGPT

Add MachineMail to ChatGPT as a plugin:

  1. Add the plugin with https://mcp.machinemail.dev/mcp as the server URL.
  2. Sign in with OAuth. Leave any client ID and secret empty: ChatGPT identifies itself with its client metadata document.
  3. A MachineMail tab opens; approve there.

ChatGPT calls MachineMail from OpenAI's servers, signs in as a public client with PKCE, and renews its tokens on its own. If you build an app that registers with Dynamic Client Registration instead, MachineMail issues it a client secret when it asks for client_secret_post or client_secret_basic.

Dots

A dot is an always-on ChatGPT agent. It uses the same plugins as ChatGPT, so once MachineMail is added as a plugin and signed in, your dot can use it with nothing more to set up. A dot has no email inbox of its own; MachineMail gives it one. Ask the dot to create an inbox, and it can send from that address, wait for replies and read them.

A dot works on its own for long stretches, so a few things are worth setting up before you leave it running:

claude.ai and Claude Desktop

Add MachineMail as a custom connector:

  1. Open Settings → Connectors and choose Add custom connector.
  2. Name it MachineMail and enter https://mcp.machinemail.dev/mcp as the URL. Leave the advanced OAuth settings empty.
  3. Choose Add, then Connect. A MachineMail tab opens; approve there.

The connector is available in claude.ai, Claude Desktop and the mobile apps. On Team and Enterprise plans, an owner adds the connector for the organization, and each member then connects it with their own MachineMail account.

To use an API key in Claude Desktop instead, start the server through the mcp-remote bridge, which needs Node.js. Open Settings → Developer → Edit Config and add:

{
  "mcpServers": {
    "machinemail": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mcp.machinemail.dev/mcp", "--header", "Authorization:${MACHINEMAIL_AUTH}"],
      "env": {
        "MACHINEMAIL_AUTH": "Bearer mm_live_..."
      }
    }
  }
}

The header value goes through env because some platforms split arguments on spaces. Restart Claude Desktop after saving. Without --header, mcp-remote signs in with OAuth instead and opens your browser on first start.

The file lives at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS and %APPDATA%\Claude\claude_desktop_config.json on Windows.

Cursor

Add the server to ~/.cursor/mcp.json for every project, or .cursor/mcp.json in one project:

{
  "mcpServers": {
    "machinemail": {
      "url": "https://mcp.machinemail.dev/mcp"
    }
  }
}

Under Settings → MCP the server shows that it needs a login. Click it, approve in the browser, and the tools appear.

For an API key, add a headers entry. Cursor reads ${env:...} from the environment it was started in, so export the variable in your shell profile:

{
  "mcpServers": {
    "machinemail": {
      "url": "https://mcp.machinemail.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${env:MACHINEMAIL_API_KEY}"
      }
    }
  }
}

VS Code

Add the server to .vscode/mcp.json in a workspace, or run MCP: Open User Configuration for every workspace:

{
  "servers": {
    "machinemail": {
      "type": "http",
      "url": "https://mcp.machinemail.dev/mcp"
    }
  }
}

Start the server from the Start button above it in the file. VS Code asks whether MachineMail may authenticate; allow it and approve in the browser. Then use the tools in Copilot's agent mode.

For an API key, prompt for it once with an input. VS Code stores it securely, so it never sits in the file:

{
  "inputs": [
    {
      "type": "promptString",
      "id": "machinemail-api-key",
      "description": "MachineMail API key",
      "password": true
    }
  ],
  "servers": {
    "machinemail": {
      "type": "http",
      "url": "https://mcp.machinemail.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${input:machinemail-api-key}"
      }
    }
  }
}

OpenCode

Add a remote server to opencode.json (global: ~/.config/opencode/opencode.json):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "machinemail": {
      "type": "remote",
      "url": "https://mcp.machinemail.dev/mcp",
      "enabled": true
    }
  }
}

OpenCode notices the 401 and signs in with OAuth. To start the sign-in yourself, run opencode mcp auth machinemail.

For an API key, set "oauth": false so OpenCode doesn't try to sign in, and add the header:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "machinemail": {
      "type": "remote",
      "url": "https://mcp.machinemail.dev/mcp",
      "enabled": true,
      "oauth": false,
      "headers": {
        "Authorization": "Bearer {env:MACHINEMAIL_API_KEY}"
      }
    }
  }
}

Check it with opencode mcp list.

Codex

Add the server to ~/.codex/config.toml (or .codex/config.toml in a trusted project). bearer_token_env_var names the environment variable that holds an API key; Codex sends it as Authorization: Bearer ...:

[mcp_servers.machinemail]
url = "https://mcp.machinemail.dev/mcp"
bearer_token_env_var = "MACHINEMAIL_API_KEY"

Codex versions with OAuth support for MCP servers can sign in instead. Leave out bearer_token_env_var and run codex mcp login machinemail. Codex identifies itself with its client metadata document and listens on a loopback port for the response.

Check it with codex mcp list, or /mcp inside a session.

Gemini CLI

Add the server to ~/.gemini/settings.json (or .gemini/settings.json in a project). httpUrl selects Streamable HTTP:

{
  "mcpServers": {
    "machinemail": {
      "httpUrl": "https://mcp.machinemail.dev/mcp"
    }
  }
}

Gemini CLI notices the 401 and signs in with OAuth on first use; to start it yourself, run /mcp auth machinemail in a session. For an API key, add "headers": { "Authorization": "Bearer $MACHINEMAIL_API_KEY" } instead.

Zed

Add a remote context server in Zed's settings.json:

{
  "context_servers": {
    "machinemail": {
      "url": "https://mcp.machinemail.dev/mcp"
    }
  }
}

Zed signs in with OAuth when the server answers 401, identifying itself with its client metadata document. For an API key, add "headers": { "Authorization": "Bearer mm_live_..." }.

Windsurf

Add the server to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "machinemail": {
      "serverUrl": "https://mcp.machinemail.dev/mcp"
    }
  }
}

Windsurf starts the OAuth sign-in when you add the server. For an API key, add "headers": { "Authorization": "Bearer ${env:MACHINEMAIL_API_KEY}" }.

Goose

Run goose configure, choose Add Extension → Remote Extension (Streamable HTTP), name it machinemail and enter https://mcp.machinemail.dev/mcp. Goose signs in with OAuth on first use, identifying itself with its client metadata document.

JetBrains IDEs

JetBrains AI Assistant connects to remote MCP servers with static headers only, so use an API key there, or run the server through mcp-remote (stdio), which signs in with OAuth:

{
  "mcpServers": {
    "machinemail": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mcp.machinemail.dev/mcp"]
    }
  }
}

JetBrains Air and the GitHub Copilot plugin sign in with OAuth directly: give them the URL only.

OpenAI Responses API and Claude Messages API

The model APIs call MCP servers for you but don't run a sign-in: pass a credential with each request. Use an API key (an inbox-scoped key keeps the model to one inbox). An OAuth access token from your own app works too, but it lasts one hour and the API won't refresh it.

curl https://api.openai.com/v1/responses \
  -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5",
    "input": "List my MachineMail inboxes.",
    "tools": [{
      "type": "mcp",
      "server_label": "machinemail",
      "server_url": "https://mcp.machinemail.dev/mcp",
      "authorization": "'"$MACHINEMAIL_API_KEY"'",
      "require_approval": "never"
    }]
  }'
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: mcp-client-2025-11-20" -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 4096,
    "messages": [{"role": "user", "content": "List my MachineMail inboxes."}],
    "mcp_servers": [{
      "type": "url",
      "url": "https://mcp.machinemail.dev/mcp",
      "name": "machinemail",
      "authorization_token": "'"$MACHINEMAIL_API_KEY"'"
    }],
    "tools": [{"type": "mcp_toolset", "mcp_server_name": "machinemail"}]
  }'

Other clients

Any client that supports remote MCP servers over Streamable HTTP works. Give it:

Setting Value
Transport Streamable HTTP (sometimes labelled http or streamable-http)
URL https://mcp.machinemail.dev/mcp
Auth OAuth (automatic in clients that support MCP authorization), or the header below
Header Authorization: Bearer mm_live_..., when the client cannot sign in or has no browser

Notes for clients that differ:

Check the connection

Ask the model to call list_inboxes. With a fresh organization it answers No inboxes yet. Use create_inbox. Then try a round trip:

Create an inbox with a one-hour TTL, tell me its address, and wait for a message to arrive.

Send mail to the address from anywhere and the model gets it back from wait_for_message, with its trust level.

If the client reports a 401:

curl -s -o /dev/null -w "%{http_code}\n" https://api.machinemail.dev/v0/inboxes -H "Authorization: Bearer $MACHINEMAIL_API_KEY"

200 means the key is good and the problem is in the client config.