Agents
Agents
Section titled “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.jsonWindows: %APPDATA%\Claude\claude_desktop_config.jsonLinux 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.jsonWindows: %APPDATA%\Cursor\mcp.json |
mcpServers |
windsurf |
~/.codeium/windsurf/mcp_config.json on every OS (on Windows ~ is %USERPROFILE%) |
mcpServers |
zed |
~/.config/zed/settings.jsonWindows: %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.
How apply merges
Section titled “How apply merges”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’senabled, …) survive the nextapply. 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,
applyfails 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,
applystops 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 created0600for every agent, because it holdsenvvalues resolved from the vault; directoriesapplyhas to create for it get0700. A symlinked config file is followed and the link is kept. - Nothing is removed.
applyonly 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.
Managed entry fields
Section titled “Managed entry fields”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.
Entry format
Section titled “Entry format”For a server stored in the registry as
name: githubcommand: npxargs: ["-y", "@modelcontextprotocol/server-github"]env: GITHUB_TOKEN: secret: github_tokeneach agent gets the following entry.
claude-code
Section titled “claude-code”{ "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.
claude-desktop
Section titled “claude-desktop”{ "mcpServers": { "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": {"GITHUB_TOKEN": "ghp_…"} } }}Restart Claude Desktop to load the change.
cursor and windsurf
Section titled “cursor and windsurf”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.
Remote servers
Section titled “Remote servers”A remote server is one stored with transport: http or transport: sse and a url, for example
name: lineartransport: httpurl: https://mcp.linear.app/mcpEach 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
Section titled “Headers”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-apitransport: httpurl: https://example.com/mcpheaders: Authorization: {secret: example_auth} X-Client: {value: mcpfleet}The secret holds the whole header value, prefix included:
printf 'Bearer %s' "$TOKEN" | mcpfleet secret set example_auth --stdinHeader 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-desktopThe count covers only the servers written. When every server is skipped, the config file is not touched.