Skip to content

Agents

mcpfleet apply <agent> writes the registry’s servers into one agent’s config file. Six agents are supported:

Agent Config file Key
claude-code ~/.claude.json, or $CLAUDE_CONFIG_DIR/.claude.json when the variable is set mcpServers
claude-desktop macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
Linux and others: ~/.config/Claude/claude_desktop_config.json, or $XDG_CONFIG_HOME/Claude/claude_desktop_config.json when XDG_CONFIG_HOME is set
mcpServers
crush $CRUSH_GLOBAL_CONFIG/crush.json, else $XDG_CONFIG_HOME/crush/crush.json, else ~/.config/crush/crush.json (also on Windows) mcp
cursor ~/.cursor/mcp.json
Windows: %APPDATA%\Cursor\mcp.json
mcpServers
windsurf ~/.codeium/windsurf/mcp_config.json on every OS (on Windows ~ is %USERPROFILE%) mcpServers
zed ~/.config/zed/settings.json
Windows: %APPDATA%\Zed\settings.json
context_servers

Once you are logged in (mcpfleet auth login), the terminal UI (mcpfleet without arguments) lists the same paths, resolved for your machine, on its Adapters tab.

Every adapter updates the file in place instead of replacing it:

  • Upsert by name. Each applied server is written under its registry name; every other entry under the key stays as it was. An existing entry with that name is updated, not replaced: mcpfleet sets the fields it manages and keeps every other field, so options you added in the agent (disabled, autoApprove, a timeout, Zed’s enabled, …) survive the next apply. See Managed entry fields.
  • Other settings are kept. Keys outside the servers key (editor settings, Claude Code’s account and project state, …) are left intact.
  • Invalid files are not touched. If the file is not valid JSON, its root is not an object, or the servers key is not an object, apply fails with an error and writes nothing. Fix the file and run it again.
  • Backup. Before overwriting an existing file, mcpfleet copies it to <file>.bak (for example ~/.cursor/mcp.json.bak). Each run replaces the previous backup.
  • Atomic write. The new content goes to a temporary file in the same directory and is then renamed over the original. If the file changes between reading and writing, apply stops with … changed during apply; re-run apply.
  • Permissions and links. An existing file keeps its permissions, and so does its .bak. A new file is created 0600 for every agent, because it holds env values resolved from the vault; directories apply has to create for it get 0700. A symlinked config file is followed and the link is kept.
  • Nothing is removed. apply only adds and updates entries. A server deleted from the registry, or left out by --tag, stays in the agent config until you remove it by hand.

A missing file (or an empty one) is created, including its directory.

These are the fields apply owns in a server entry. In an existing entry it removes all of them first and then writes the ones the server has, so a managed field the server no longer has disappears: a server without env in the registry loses its old env, and a server that switches between local and remote loses the fields of its old transport. env and headers are replaced as a whole. Any other field in the entry is kept.

Agent Managed fields
cursor command, args, env, url, headers, type
claude-code command, args, env, url, headers, type
claude-desktop command, args, env
windsurf command, args, env, serverUrl, url, headers, type
zed command, args, env, url, headers
crush command, args, env, url, headers, type

Cursor and Windsurf never write type, and Windsurf writes serverUrl rather than its alias url; both are still managed, so a stale value cannot contradict the new entry. In Zed’s settings.json the comments inside an applied entry are kept as well. A Zed extension entry ("source": "extension") with the same name as a registry server gets command written beside its settings; rename the server in the registry if you want to keep the extension’s entry apart.

For a local (stdio) server, only name, command, args and env are written: the entry describes a process the agent starts. args and env are left out of an entry when the server has none. A remote server (transport: http or sse) gets the agent’s remote entry instead, with its url, its headers and no command; see Remote servers. install, tags and platforms are never written to agent config files.

For a server stored in the registry as

name: github
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN:
secret: github_token

each agent gets the following entry.

{
"mcpServers": {
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "ghp_…"}
}
}
}

The servers go into the top-level mcpServers of ~/.claude.json, which is Claude Code’s user scope: they are available in every project. The rest of Claude Code’s state in that file is kept.

When CLAUDE_CONFIG_DIR is set, the file is $CLAUDE_CONFIG_DIR/.claude.json. The variable must be an absolute path; with a relative one, apply claude-code stops with claude-code: CLAUDE_CONFIG_DIR must be an absolute path (got "…") and writes nothing.

Claude Code also writes this file while it runs. Run apply claude-code while Claude Code is closed, or start a new session afterwards and check the result with claude mcp list.

{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "ghp_…"}
}
}
}

Restart Claude Desktop to load the change.

Same entry as claude-desktop, under mcpServers in mcp.json (Cursor) or mcp_config.json (Windsurf).

{
"mcp": {
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "ghp_…"}
}
}
}

mcpfleet writes Crush’s global config, crush.json, under the mcp key, and every entry gets "type": "stdio". A project-level crush.json is not touched.

{
// your other settings stay as they are
"theme": "One Dark",
"context_servers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "ghp_…"}
}
}
}

This is the flat format of the Zed MCP docs: command is the executable, with args and env beside it. Older Zed settings nest them as "command": {"path": …, "args": …, "env": …}; Zed rewrites such entries to the flat format itself. mcpfleet reads both formats, and an entry it applies replaces an old nested one under the same name with the flat format.

Zed’s settings.json may contain comments and trailing commas. mcpfleet edits it on its syntax tree, so comments, formatting and every other setting survive; only the applied entries under context_servers change, indented like the rest of the file.

A remote server is one stored with transport: http or transport: sse and a url, for example

name: linear
transport: http
url: https://mcp.linear.app/mcp

Each agent gets the entry format its own config file uses for remote servers. Agents whose config file cannot hold that transport skip the server:

Agent http sse Format source
claude-code {"type": "http", "url": …, "headers": …} {"type": "sse", "url": …, "headers": …} Claude Code MCP docs (claude mcp add --header)
claude-desktop not supported not supported claude_desktop_config.json holds local servers only; add remote servers in Claude Desktop under Settings → Connectors
crush {"type": "http", "url": …, "headers": …} {"type": "sse", "url": …, "headers": …} Crush config schema (type is stdio, sse or http; headers for http and sse)
cursor {"url": …, "headers": …} {"url": …, "headers": …} Cursor MCP docs; Cursor picks Streamable HTTP or SSE itself
windsurf {"serverUrl": …, "headers": …} {"serverUrl": …, "headers": …} Windsurf MCP docs; Windsurf picks the transport from the server
zed {"url": …, "headers": …} not supported Zed MCP docs; Zed has no SSE transport for context servers

A remote entry carries the address and the HTTP headers: no command, args or env. The agent connects to the server instead of starting a process, so a remote server’s env is not written and its secrets are not read from the vault. headers is left out of the entry when the server has none.

headers maps each HTTP header to a literal value or a secret from the local vault, like env. apply replaces every secret reference with its value before writing, so the agent’s config file holds the token itself, in a file created with 0600 permissions. A referenced secret that is not in the vault stops apply with an error naming the server and the header, and nothing is written.

name: remote-api
transport: http
url: https://example.com/mcp
headers:
Authorization: {secret: example_auth}
X-Client: {value: mcpfleet}

The secret holds the whole header value, prefix included:

Terminal window
printf 'Bearer %s' "$TOKEN" | mcpfleet secret set example_auth --stdin

Header values are written as they are. Some agents expand variables in them, so a value containing $ is interpreted by the agent:

Agent Expanded in header values
claude-code ${VAR} and ${VAR:-default}
crush $VAR and $(command) (shell expansion)
cursor, windsurf ${env:NAME}
zed not documented

Every agent that mcpfleet writes remote servers for reads headers. Without an Authorization header, an agent that supports OAuth for remote servers (Claude Code, Crush, Zed) asks you to sign in on first use; Zed starts its OAuth flow only when no Authorization header is configured.

For the linear server above, apply claude-code writes

{
"mcpServers": {
"linear": {"type": "http", "url": "https://mcp.linear.app/mcp"}
}
}

and apply windsurf writes

{
"mcpServers": {
"linear": {"serverUrl": "https://mcp.linear.app/mcp"}
}
}

For the remote-api server above, with example_auth set to Bearer tok…, apply claude-code writes

{
"mcpServers": {
"remote-api": {
"type": "http",
"url": "https://example.com/mcp",
"headers": {"Authorization": "Bearer tok…", "X-Client": "mcpfleet"}
}
}
}

When an agent does not support a server’s transport, or a remote server has no url, apply leaves that server out, prints a warning on stderr and writes the rest:

warning: skipping linear: claude-desktop does not support http servers in its config file
✓ Applied 2 MCP server(s) to claude-desktop

The count covers only the servers written. When every server is skipped, the config file is not touched.