Skip to content

Client setup

Every MCP client starts or reaches the same server. What changes from one client to the next is the file it reads, the key it keeps servers under, how it passes environment variables and headers, and whether it can ask the user a question in the middle of a call. This page has the entry for each client in each of the three ways to run arcane-mcp. Getting started takes you from an Arcane API key to the first prompt; every setting named here is in Configuration.

WayWhat the client doesWhere the Arcane credential lives
Binary over stdioStarts arcane-mcp as a child process and speaks MCP over its standard input and outputARCANE_URL and ARCANE_API_KEY in the entry’s environment (or a .env file in the server’s working directory)
Docker over stdioStarts docker run -i --rm … ghcr.io/jmrplens/arcane-mcp:latest as that child processThe same variables, forwarded into the container with -e
Remote Streamable HTTPSends every request to a server started with --http (or ARCANE_MCP_HTTP_ADDR), at /mcpAuthorization: Bearer <ARCANE_MCP_HTTP_TOKEN> and, per user, X-Arcane-API-Key: <Arcane key> on every request; on a server in apikey mode only Authorization: Bearer <Arcane key>

Before you pick an entry:

  • The binary comes from any channel on Installation (the release binary, Homebrew, winget, npm, PyPI, NuGet, go install), or from a build as in Getting started. Give clients its absolute path: desktop applications do not inherit your shell’s PATH. With npx, uvx or dnx, the command and arguments are on that channel’s page and the environment below stays the same.

  • The Docker image is ghcr.io/jmrplens/arcane-mcp, published for linux/amd64 and linux/arm64 by the release workflow on every version tag (X.Y.Z, X.Y and latest; see Docker); pin a version tag where you can. It serves stdio unless ARCANE_MCP_HTTP_ADDR is set. In a stdio entry:

    • keep -i (the protocol travels over standard input) and never add -t or -d;
    • -e NAME with no value forwards that variable from the environment the client starts docker with, so the entry’s env block carries the values and the arguments only carry the names. Every extra setting (ARCANE_MCP_TOOL_SURFACE, ARCANE_MCP_READ_ONLY…) needs both its own -e NAME and a value in env;
    • ARCANE_URL must be reachable from inside the container. localhost there is the container itself: use the host’s LAN name, host.docker.internal (Docker Desktop), or --network host (Linux);
    • the container does not see a .env file in your working directory.
  • An HTTP entry needs a server already running in HTTP mode, for example:

    Terminal window
    export ARCANE_MCP_HTTP_TOKEN=$(openssl rand -hex 32)
    docker run -d --name arcane-mcp -p 127.0.0.1:8080:8080 \
    -e ARCANE_URL=https://arcane.example.com \
    -e ARCANE_MCP_HTTP_ADDR=:8080 \
    -e ARCANE_MCP_HTTP_ALLOW_PLAINTEXT=true \
    -e ARCANE_MCP_HTTP_TOKEN \
    ghcr.io/jmrplens/arcane-mcp:latest

    or ./arcane-mcp --http 127.0.0.1:8080 with the same variables. The examples below use https://arcane-mcp.example.com/mcp, a TLS-terminating reverse proxy in front of that listener; the server itself speaks plain HTTP, which ARCANE_MCP_HTTP_ALLOW_PLAINTEXT=true declares: inside the container it binds every interface, the port is published on the host’s loopback only, and without the variable it logs a warning at startup (see Plain HTTP and the hardened mode). Set ARCANE_MCP_HTTP_ALLOWED_HOSTS=arcane-mcp.example.com when the proxy forwards that public name: on a loopback listener the server otherwise answers only loopback Host values. Each user sends their own Arcane key in X-Arcane-API-Key and acts with its permissions; without the header the server falls back to its own ARCANE_API_KEY (or username and password) and refuses the request when it has none. A session stays bound to the key it was opened with, so the client must send the same headers on every request, which every client below does. HTTP mode covers the limits, allowed hosts and the startup warnings.

  • A server in apikey mode (ARCANE_MCP_HTTP_AUTH=apikey, see HTTP authentication) needs one secret per user: put the user’s Arcane API key where the entries below put the token (Authorization: Bearer <Arcane key>) and leave out X-Arcane-API-Key. That suits clients that can only set Authorization. A connector whose headers an organisation shares (see Claude Desktop below) then shares that key’s Arcane permissions with everyone.

  • Keep secrets off command lines. ARCANE_MCP_HTTP_TOKEN and ARCANE_API_KEY deliberately have no flag. Where a client can prompt for a value or expand an environment variable, its entry below does that; elsewhere the value sits in the client’s configuration file, so protect that file.

ClientConfigurationstdioStreamable HTTPElicitationNotes
Claude Desktopclaude_desktop_config.json, mcpServersYesCustom connector only (public HTTPS)Not documentedRemote connectors are reached from Anthropic’s cloud, not from your computer
Claude Codeclaude mcp add, or .mcp.json with mcpServersYesYesYesExpands ${VAR} in .mcp.json
VS Code (GitHub Copilot).vscode/mcp.json with servers, or .mcp.json with mcpServersYesYesYes (since 1.102)At most 128 tools per chat request
Cursor.cursor/mcp.json or ~/.cursor/mcp.json, mcpServersYesYesYesExpands ${env:NAME}
Windsurf (Devin Desktop)mcp_config.json, mcpServersYesYesNot documentedRenamed Devin Desktop in 2026; see the section for the file location
Zedsettings.json, context_serversYesYesNoZed documents tools and prompts only
Clinecline_mcp_settings.json, mcpServersYesYes ("type": "streamableHttp")Not documentedWithout type, a remote entry is treated as legacy SSE
Continueconfig.yaml or .continue/mcpServers/*.yaml, mcpServers listYesYes (type: streamable-http)Not documentedSecrets via ${{ secrets.NAME }}
LM Studiomcp.json, mcpServers (Cursor’s notation)YesYesNot documented
Kiro.kiro/settings/mcp.json or ~/.kiro/settings/mcp.json, mcpServersYesYesYes${VAR} only after you approve the variable
Gemini CLI~/.gemini/settings.json or .gemini/settings.json, mcpServersYesYes (httpUrl)Not documentedStrips secret-looking variables it does not see in env
OpenAI Codex CLI~/.codex/config.toml, [mcp_servers.<name>]YesYesYesTOML, not JSON
JetBrains AI AssistantSettings dialog, JSON with mcpServersYesURL only, no headers documentedNot documentedReach a token-protected server through mcp-remote
Goose~/.config/goose/config.yaml, extensionsYesYes (type: streamable_http)Yes (form mode)

The client facts on this page were checked against each client’s own documentation in October 2026; clients change quickly, so when an entry stops working, compare it with the client’s current documentation first.

Destructive actions (deletes, prunes, kills, restores, redeploys…) never run without approval. On a client that supports MCP elicitation, a destructive call without confirm: true makes the server ask the user directly, in the client’s own interface; on the others the call is refused with a message telling the model to ask the user and repeat it with confirm: true. “Not documented” above means the client’s documentation does not say either way: arcane-mcp reads the capability the client declares when it connects, so nothing needs configuring, and a client without it gets the refusal. See Confirmation of destructive actions; ARCANE_MCP_ELICITATION=off turns the question off for every client.

ARCANE_MCP_TOOL_SURFACE (or --tool-surface) decides how many tools the client sees; every surface reaches all 461 actions. See Tool surfaces.

  • dynamic (the default, 2 tools, about 400 tokens) suits every client and is the one to keep on clients that cap the number of tools or have a small context window: VS Code accepts at most 128 tools per chat request, and local models in LM Studio or Goose pay for every tool definition in their context.
  • meta (52 tools, about 16 k tokens) fits under those caps and lets the model read every action name up front.
  • individual (461 tools, about 111 k tokens) exceeds VS Code’s limit and is heavy for most clients; use it with exclusions (ARCANE_MCP_EXCLUDE_TOOLS) when you want per-tool approvals or allow-lists in the client.

Several clients also filter tools on their side (Codex enabled_tools, Gemini CLI includeTools, Goose available_tools, Kiro and Windsurf per-tool toggles); ARCANE_MCP_EXCLUDE_TOOLS does the same on the server, for every client at once.

Add these to the entry’s env block (and, for the Docker form, a matching -e NAME argument), or as flags in args for the binary:

SettingFlagEffect
ARCANE_MCP_READ_ONLY=true--read-onlyOnly read-only actions exist; nothing can change Arcane
ARCANE_MCP_SAFE_MODE=true--safe-modeMutating calls return a preview of the exact request instead of running
ARCANE_MCP_EXCLUDE_TOOLS=swarm_node,image.prune--exclude-toolsHides domains, actions or tool names
ARCANE_MCP_TOOL_SURFACE=meta--tool-surface metaPicks the surface

In HTTP mode these are set where the server runs, not in the client entry, and apply to every user of that deployment. Safety explains each one.

The Claude Desktop extension installs the server from one .mcpb file with a settings form, no JSON to edit. The entries below are for running it without the extension.

Open Settings → Developer → Edit Config, which opens (and creates) claude_desktop_config.json: ~/Library/Application Support/Claude/ on macOS, %APPDATA%\Claude\ on Windows, ~/.config/Claude/ on Linux. Quit Claude Desktop completely and start it again after editing; it loads servers at startup only.

claude_desktop_config.json
{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "arc_..."
}
}
}
}

Claude Code keeps a server in ~/.claude.json for the current project by default; --scope user makes it available in every project and --scope project writes it to .mcp.json in the repository. In .mcp.json it expands ${VAR} and ${VAR:-default} in command, args, env, url and headers, which keeps keys out of a shared file.

Terminal window
claude mcp add arcane \
--env ARCANE_URL=https://arcane.example.com \
--env ARCANE_API_KEY=arc_... \
-- /path/to/arcane-mcp

--env puts the key in your shell history. To avoid that, write the entry in .mcp.json and export ARCANE_API_KEY in the shell you start claude from:

.mcp.json
{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "${ARCANE_API_KEY}"
}
}
}
}

VS Code reads .vscode/mcp.json in the workspace, or the mcp.json of your user profile (MCP: Open User Configuration), and keeps servers under servers. That format supports inputs: an input with "password": true prompts for the key once and stores it, so it never lands in the file. VS Code also reads a portable format, .mcp.json at the workspace root or ~/.copilot/mcp-config.json, with servers under mcpServers, which other Copilot tools share but which does not support inputs.

.vscode/mcp.json
{
"inputs": [
{ "type": "promptString", "id": "arcane-api-key", "description": "Arcane API key", "password": true }
],
"servers": {
"arcane": {
"type": "stdio",
"command": "/path/to/arcane-mcp",
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "${input:arcane-api-key}"
}
}
}
}

A chat request carries at most 128 tools, counting every server and built-in tool, so keep dynamic or meta here (see Which surface for which client). Copilot sessions run by the Agent Host do not receive servers that need an ${input:…} prompt; for those, use the portable .mcp.json format.

Cursor reads .cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project. It expands ${env:NAME} (and ${userHome}, ${workspaceFolder}) in command, args, env, url and headers, so the entries below read the secrets from the environment Cursor starts from.

.cursor/mcp.json
{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "${env:ARCANE_API_KEY}"
}
}
}
}

Windsurf was renamed Devin Desktop in June 2026. Its Cascade agent’s Open MCP config file action opens ~/.config/devin/mcp_config.json (%APPDATA%\devin\mcp_config.json on Windows), the same user file the Devin Local agent and the Devin CLI read; builds from before the rename read ~/.codeium/windsurf/mcp_config.json. Use the file that action opens on your installation. Servers sit under mcpServers. A remote server takes url (Cascade also accepts serverUrl, the key older Windsurf builds documented). Press the refresh button in the MCP panel after editing.

mcp_config.json
{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "arc_..."
}
}
}
}

On Teams and Enterprise plans an admin may have to enable MCP, and once a single server is allowlisted every other one is blocked.

Zed keeps servers under context_servers in its settings.json (zed: open settings file; ~/.config/zed/settings.json on macOS and Linux), or in .zed/settings.json in a project. It does not document environment-variable expansion in these entries, so the values are written in the file.

settings.json
{
"context_servers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"args": [],
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "arc_..."
}
}
}
}

Zed documents support for MCP tools and prompts, not elicitation, so destructive calls need confirm: true there.

In the Cline panel, select the MCP Servers icon, open the Configure tab and select Configure MCP Servers; that opens cline_mcp_settings.json, wherever your installation keeps it. Cline adds its own disabled and autoApprove keys; leave autoApprove empty or limited to read-only tools.

cline_mcp_settings.json
{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "arc_..."
},
"disabled": false,
"autoApprove": []
}
}
}

Continue reads ~/.continue/config.yaml; a workspace can add servers as block files in .continue/mcpServers/, which need the name, version and schema header shown here. ${{ secrets.NAME }} is resolved by Continue itself, so the file never holds the key. MCP tools are available in agent mode only; reload the Continue window after editing.

.continue/mcpServers/arcane.yaml
name: Arcane
version: 0.0.1
schema: v1
mcpServers:
- name: arcane
command: /path/to/arcane-mcp
env:
ARCANE_URL: https://arcane.example.com
ARCANE_API_KEY: ${{ secrets.ARCANE_API_KEY }}

LM Studio follows Cursor’s mcp.json notation. Open the file from the Program tab of the right-hand sidebar with Install → Edit mcp.json. Its documentation does not describe environment-variable expansion, so the values are written in the file. LM Studio asks before each tool call unless you allow a tool permanently.

mcp.json
{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "arc_..."
}
}
}
}

Local models have small context windows; keep the dynamic surface here.

Kiro reads .kiro/settings/mcp.json in the workspace, or ~/.kiro/settings/mcp.json for every workspace; with both, workspace entries win. It expands ${VARIABLE_NAME}, but the IDE only does so for variables you have approved in the warning it shows when an entry first uses them. Users have reported that the Kiro CLI leaves ${…} unexpanded in some versions; if the server answers 401, write the values instead. Changes apply when you save.

.kiro/settings/mcp.json
{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"args": [],
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "${ARCANE_API_KEY}"
},
"disabled": false
}
}
}

Gemini CLI reads ~/.gemini/settings.json, or .gemini/settings.json in a project, and expands $VAR and ${VAR} in its string values. When it starts a stdio server it removes from the inherited environment every variable whose name looks secret (*KEY*, *TOKEN*, *PASSWORD*, *AUTH*…), so ARCANE_API_KEY reaches the server only if the entry’s env names it, as below.

~/.gemini/settings.json
{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"env": {
"ARCANE_URL": "https://arcane.example.com",
"ARCANE_API_KEY": "$ARCANE_API_KEY"
}
}
}
}

Leave the entry’s trust option unset: it skips Gemini CLI’s own confirmation before every tool call.

Codex reads ~/.codex/config.toml, or .codex/config.toml in a trusted project; the ChatGPT desktop app and the Codex IDE extension share the same file. Each server is a [mcp_servers.<name>] table.

~/.codex/config.toml
[mcp_servers.arcane]
command = "/path/to/arcane-mcp"
env_vars = ["ARCANE_API_KEY"]
tool_timeout_sec = 1800
[mcp_servers.arcane.env]
ARCANE_URL = "https://arcane.example.com"

env_vars forwards ARCANE_API_KEY from the environment Codex runs in, so the file never holds it; put it in the env table instead if you prefer.

Codex gives a tool call 60 seconds unless tool_timeout_sec says otherwise, which cuts off image pulls, builds and redeploys; the entries above raise it to match the server’s own ARCANE_MCP_ACTION_TIMEOUT default of 30 minutes. Codex supports elicitation; with a granular approval_policy, keep mcp_elicitations = true, or Codex rejects the server’s confirmation question and the destructive call is refused.

In IntelliJ IDEA, GoLand, PyCharm and the other JetBrains IDEs, open Settings → Tools → AI Assistant → Model Context Protocol (MCP), select Add, choose the transport and paste the JSON. Choose whether the server is available globally or for the current project, then select OK and Apply. Import from Claude copies the servers already in Claude Desktop’s configuration.

JetBrains documents an stdio entry as command and args only, plus a Working directory field. arcane-mcp reads a .env file in its working directory, so put the credentials there rather than in the JSON:

/path/to/arcane-config/.env
ARCANE_URL=https://arcane.example.com
ARCANE_API_KEY=arc_...

Set Working directory to the folder that holds that .env file (/path/to/arcane-config), and paste:

{
"mcpServers": {
"arcane": {
"command": "/path/to/arcane-mcp",
"args": []
}
}
}

Goose keeps MCP servers as extensions under extensions in ~/.config/goose/config.yaml (%APPDATA%\Block\goose\config\config.yaml on Windows). goose configure → Add Extension writes the same entry interactively, and can store secrets in the system keyring (env_keys) instead of the file. Goose supports elicitation in form mode.

~/.config/goose/config.yaml
extensions:
arcane:
name: arcane
type: stdio
enabled: true
cmd: /path/to/arcane-mcp
args: []
envs:
ARCANE_URL: https://arcane.example.com
ARCANE_API_KEY: arc_...
timeout: 1800

timeout is how long Goose waits for one tool call, in seconds; the entries above raise the usual 300 so that long image pulls and redeploys can finish.

mcp-remote is a small Node.js program a client starts as an stdio server and that forwards everything to a remote one. It is the way to reach an HTTP deployment from a client that cannot send headers (JetBrains), or that only connects to public URLs (Claude Desktop’s connectors). It expands ${NAME} in its arguments from its own environment, so the secrets sit in the entry’s env block:

{
"mcpServers": {
"arcane": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://arcane-mcp.example.com/mcp",
"--header", "Authorization:${ARCANE_AUTH}",
"--header", "X-Arcane-API-Key:${ARCANE_API_KEY}"
],
"env": {
"ARCANE_AUTH": "Bearer change-me",
"ARCANE_API_KEY": "arc_..."
}
}
}
}

Write each header with no space after the colon and keep the space inside the variable: some clients do not quote arguments that contain spaces when they start npx. mcp-remote refuses plain http:// URLs other than localhost unless you add --allow-http.

  • The server does not start. Run the entry’s command in a terminal with the same variables. A missing ARCANE_URL, or in stdio mode a missing ARCANE_API_KEY (or username and password), is reported and the process exits.
  • Every tool call fails with a 401 from Arcane. Arcane rejected the key: in stdio mode check ARCANE_API_KEY; over HTTP, the key in the client’s X-Arcane-API-Key header (or the server’s own ARCANE_API_KEY when the client sends none), or with ARCANE_MCP_HTTP_AUTH=apikey the key the client sends as its Authorization: Bearer token. The error’s hint names the credential that was sent: ARCANE_API_KEY (or the login) when it was the server’s own, the X-Arcane-API-Key header when it was the client’s, and the client’s Bearer token with ARCANE_MCP_HTTP_AUTH=apikey.
  • The HTTP connection itself is refused with 401. A 401 with WWW-Authenticate: Bearer before any MCP traffic means the Authorization header is missing or does not match ARCANE_MCP_HTTP_TOKEN; with ARCANE_MCP_HTTP_AUTH=apikey, that the Bearer is missing or Arcane does not accept it as an API key.
  • The Docker form hangs or exits at once. Check that -i is there, that no -t or -d crept in, and that ARCANE_URL resolves from inside the container.
  • The client shows no tools or too many. See Which surface for which client.
  • Destructive calls keep coming back asking for confirmation. That is the refusal for clients without elicitation; the model should ask you and repeat the call with confirm: true. See Safety.
  • Anything else. Troubleshooting quotes the errors you are likely to meet, from startup to HTTP refusals, with their cause and fix.