Skip to content

Install MCP servers

apm install is the same driver for two artifact kinds: APM packages (see Install Packages) and MCP servers. This page covers MCP servers: how you declare them, what gets written to each runtime, and how tokens get injected.

Terminal window
apm install --mcp io.github.github/github-mcp-server

This adds one entry under dependencies.mcp: in apm.yml and writes a runtime-specific MCP config file for every detected harness.

MCP servers live under dependencies.mcp: (or devDependencies.mcp:). Three forms are valid — pick the one that matches the source you have:

dependencies:
mcp:
# 1. Registry reference (bare string)
- io.github.github/github-mcp-server
# 2. Self-defined stdio (local process)
- name: filesystem
registry: false
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
# 3. Self-defined remote (HTTP / SSE)
- name: linear
registry: false
transport: http
url: https://mcp.linear.app/sse
headers:
Authorization: "Bearer ${LINEAR_TOKEN}"

The full grammar (overlays, ${input:...} variables, tools: allowlists, package: selection) is in Package Anatomy.

apm install --mcp NAME writes the entry into apm.yml for you, then runs install. Three shapes match the three manifest forms:

Terminal window
# Registry
apm install --mcp io.github.github/github-mcp-server
# stdio (everything after `--` is the spawn command)
apm install --mcp filesystem -- npx -y @modelcontextprotocol/server-filesystem /workspace
# Remote
apm install --mcp linear --transport http --url https://mcp.linear.app/sse

apm mcp install NAME ... is an alias that forwards to the same code path. The apm mcp group also provides search, list, and show for discovery — see the CLI reference.

For every harness APM detects in your environment, apm install writes a runtime-specific MCP config file. The schemas differ; the apm.yml source of truth does not.

HarnessFileScopeFormat
GitHub Copilot CLI~/.copilot/mcp-config.jsonglobalJSON mcpServers
VS Code (Copilot).vscode/mcp.jsonprojectJSON servers
Claude Code.mcp.json (project) or ~/.claude.json (-g)bothJSON mcpServers
Cursor.cursor/mcp.jsonproject (only if .cursor/ exists)JSON mcpServers
Codex CLI~/.codex/config.tomlglobalTOML [mcp_servers.*]
Gemini CLI.gemini/settings.jsonproject (only if .gemini/ exists)JSON mcpServers
OpenCodeopencode.jsonproject (only if .opencode/ exists)JSON mcp
Windsurf~/.codeium/windsurf/mcp_config.jsonglobalJSON mcpServers

Cursor, Gemini, and OpenCode are opt-in by directory: APM only writes their config when the corresponding .cursor/, .gemini/, or .opencode/ directory already exists in the project. This avoids creating runtime artifacts for tools you do not use.

apm install -g --mcp NAME is restricted to the two harnesses with true global MCP support: Copilot CLI and Codex CLI. The other runtimes are project-scoped.

MCP defines two transport families. APM exposes both:

  • stdio — APM (and your harness) spawns a local process and speaks MCP over its stdio. Requires command: and optional args:. Use --env KEY=VALUE (repeatable) for environment variables. Servers do not go through a shell, so $VAR and backticks in args are passed literally.
  • http / sse / streamable-http — APM points your harness at a remote endpoint. Requires url: (http or https only — websockets and file:// are rejected). Use --header KEY=VALUE (repeatable) for HTTP headers such as Authorization.

--transport is inferred when omitted: a --url implies a remote transport, a post--- command implies stdio. The mutually-exclusive combinations (--url plus stdio command, --header without --url, etc.) are rejected with exit code 2.

APM does not template arbitrary environment variables into MCP config files (your harness does that at runtime). It does inject one specific credential automatically:

When the Copilot CLI adapter writes a remote MCP config and the server is identified as the GitHub MCP server, APM resolves a token and adds an Authorization: Bearer <token> header.

The server is identified as “GitHub” by two narrow checks (copilot.py:1208):

  1. The server name (case-insensitive) is one of: github-mcp-server, github, github-mcp, github-copilot-mcp-server.
  2. Or the parsed URL hostname matches the GitHub host allowlist (github.com, api.github.com, and registered GHES hostnames).

This is an exact-match allowlist on hostname, not a substring check. A URL like https://github.com.evil.example does not match because the parsed hostname is github.com.evil.example, not github.com.

The token is resolved from this chain (first non-empty wins):

  1. GITHUB_COPILOT_PAT
  2. GITHUB_TOKEN
  3. GITHUB_APM_PAT
  4. GITHUB_PERSONAL_ACCESS_TOKEN (Copilot CLI compat)

If none are set, no header is injected and the server is written without auth — you will get an unauthenticated request at runtime. For other authenticated remote servers, set headers explicitly with --header Authorization="Bearer ${MY_TOKEN}".

Re-run apm install --mcp NAME ... against an existing entry:

SituationBehaviour
New NAMEAppended to dependencies.mcp.
Existing NAME, identical configNo-op. Logs unchanged.
Existing NAME, different config, TTYPrompts to replace.
Existing NAME, different config, CIRefuses with exit 2. Re-run with --force.

Use --dry-run to preview the manifest change without writing.

The apm mcp group is for discovery and standalone install:

apm mcp search <query> # search the configured registry
apm mcp list # list available servers
apm mcp show <name> # detailed server info
apm mcp install <name> # alias for `apm install --mcp <name>`

Full flag tables and exit codes: CLI reference.

  • Authoring an MCP server as a primitive of your own package — see the producer ramp.
  • Lockfile and trust boundary for transitive MCP servers — Lifecycle.