--- summary: 'How mcporter discovers, merges, and mutates configuration files (project + home + imports), including OAuth and persistence.' read_when: - 'Working on config resolution, imports, or mcporter config subcommands' --- # CLI Help Menu Snapshot ``` mcporter config Usage: mcporter config [options] Manage configured MCP servers, imports, and ad-hoc discoveries. Commands: list [options] [filter] Show merged servers (local + imports + ad-hoc cache) get Inspect a single server with resolved source info add [options] [target] Persist a server definition (URL or stdio command) remove [options] Delete a local entry or copy from an import import [options] Copy entries from cursor/claude/codex/etc. into config login Complete OAuth/auth flows for a server logout Clear cached credentials for a server doctor [options] Validate config files and report common mistakes help [command] Show CLI or subcommand help Global Options: --config Use an explicit config file (default: config/mcporter.json) --root Set project root for import discovery (default: cwd) --json Emit machine-readable output when supported -h, --help Display help for mcporter config Run `mcporter config help add` to see transport flags, ad-hoc persistence tips, and schema docs. See https://github.com/sweetistics/mcporter/blob/main/docs/config.md for config anatomy, import precedence, and troubleshooting guidance. ``` # Configuration Guide ## Overview mcporter keeps three configuration buckets in sync: repository-scoped JSON (`config/mcporter.json`), imported editor configs (Cursor, Claude, Codex, Windsurf, OpenCode, VS Code), and ad-hoc definitions supplied on the CLI. This guide explains how those sources merge, how to mutate them with `mcporter config ...`, and the safety rails around OAuth, env interpolation, and persistence. ## Quick Start 1. Create `config/mcporter.json` at the repo root: ```jsonc { "$schema": "https://raw.githubusercontent.com/steipete/mcporter/main/mcporter.schema.json", "mcpServers": { "linear": { "description": "Linear issues", "baseUrl": "https://mcp.linear.app/mcp", "headers": { "Authorization": "Bearer ${LINEAR_API_KEY}" }, }, }, "imports": ["cursor", "claude-code", "claude-desktop", "codex", "windsurf", "opencode", "vscode"], } ``` The `$schema` property enables IDE autocomplete and validation. Use the raw GitHub URL for the latest schema, or copy `mcporter.schema.json` locally. 2. Run `mcporter list linear` (or `mcporter config list linear`) to make sure the runtime can reach it. 3. Use `mcporter config add shadcn https://www.shadcn.io/api/mcp` to persist another server without editing JSON. 4. Authenticate any OAuth-backed server with either `mcporter auth ` or `mcporter config login `; tokens land in the shared vault (`~/.mcporter/credentials.json`, or `$XDG_DATA_HOME/mcporter/credentials.json` when set) unless you override `tokenCacheDir`. ## Config Resolution Order mcporter now merges home and project config files by default so global servers stay available inside repos. The order depends on how you invoke the CLI: 1. If you pass `--config ` (or set `--config` programmatically), only that file is used—no merging. 2. If `MCPORTER_CONFIG` is set, only that file is used—no merging. 3. Otherwise, mcporter loads both of these layers (when present): - `$XDG_CONFIG_HOME/mcporter/mcporter.json[c]` when `XDG_CONFIG_HOME` is set, falling back to `~/.mcporter/mcporter.json[c]` when no XDG mcporter config exists - `/config/mcporter.json` Entries from the project file override entries with the same name from the home file. Each layer still pulls in its own imports before merging. All `mcporter config …` mutations still write back to a single file: the explicit path when provided; otherwise the project config path (`/config/mcporter.json`). To edit the home file explicitly, run commands like `mcporter config --config ~/.mcporter/mcporter.json add …` or set `MCPORTER_CONFIG` in your shell profile. mcporter honors XDG Base Directory env vars for its own paths when they are explicitly set to absolute paths: | Kind | Env var | mcporter path | Legacy fallback | | ------ | ----------------- | ----------------------------------------------- | -------------------- | | config | `XDG_CONFIG_HOME` | `$XDG_CONFIG_HOME/mcporter/mcporter.json[c]` | `~/.mcporter/...` | | data | `XDG_DATA_HOME` | `$XDG_DATA_HOME/mcporter/credentials.json` | `~/.mcporter/...` | | cache | `XDG_CACHE_HOME` | `$XDG_CACHE_HOME/mcporter//schema.json` | `~/.mcporter/...` | | state | `XDG_STATE_HOME` | `$XDG_STATE_HOME/mcporter/daemon/...` | `~/.mcporter/daemon` | Unset, empty, or relative XDG vars fall back to `~/.mcporter` for backwards compatibility. For config files only, an absolute `XDG_CONFIG_HOME` is XDG-first but still probes `~/.mcporter/mcporter.json[c]` when no XDG mcporter config exists, so embedders that sandbox unrelated tools with `XDG_CONFIG_HOME` do not accidentally hide the user's registry. Explicit overrides still win: `--config`/`MCPORTER_CONFIG` for config files, `tokenCacheDir` for per-server OAuth/schema cache directories, and `MCPORTER_DAEMON_DIR` for daemon files. ## Discovery & Precedence mcporter builds a merged view of all known servers before executing any command. The sources load in this order: | Priority | Source | Notes | | -------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | Explicit `--http-url`, `--stdio`, or bare URL passed to commands | Highest priority, never cached unless `--persist` is supplied. Requires `--allow-http` for plain HTTP URLs. | | 2 | `config/mcporter.json` (or the file passed via `--config`) | Default path is `/config/mcporter.json`; missing file returns an empty config so commands continue to work. | | 3 | Imports listed in `"imports"` | When you omit `imports`, mcporter loads `['cursor','claude-code','claude-desktop','codex','windsurf','opencode','vscode']`. When you specify a non-empty array, mcporter appends any omitted defaults after your list so shared presets remain available. | Rules: - Later sources never override earlier ones. Local config always wins over imports; ad-hoc descriptors override both for the duration of a command. - Each merged server tracks its origin (local path vs. import path), so `mcporter config get ` can show you the path before you edit or remove the local copy with `mcporter config remove `. - Imports remain read-only until you explicitly copy an entry via `mcporter config import --copy` or run `mcporter config add --copy-from claude-code:linear` (feature planned alongside the CLI work). ## CLI Workflows `mcporter config` is the entry point for reading and writing configuration files. Use the existing ad-hoc flags on `mcporter list|call|auth` when you want ephemeral definitions; once you’re ready to persist them, switch back to `mcporter config add`. Use `--scope home|project` with `mcporter config add` to pick the write target explicitly. `project` is always the default (creating `config/mcporter.json` if needed); `home` writes to the XDG config path when `XDG_CONFIG_HOME` is set, otherwise `~/.mcporter/mcporter.json`, even when a project config is present. `--persist ` still takes precedence when you need a custom file. ### `mcporter config list [filter]` - Shows **local** entries by default. Pass `--source import` to list imported editor configs, or `--json` for machine output. - Always appends a summary of other config files (paths, counts, sample names) so you know where imported entries live. - `filter` accepts a name, glob fragment, or `source:cursor` selector. - Adds informational notes when we auto-correct names (same machinery as `mcporter list`). ### `mcporter config get ` - Prints the resolved definition for a single server, including the on-disk path, inherited headers/env, and transport details. - Near-miss names are auto-corrected with the same heuristics as `mcporter list`/`call`, and you’ll see suggestions whenever ambiguity remains. - Supports ad-hoc descriptors so you can inspect a URL before persisting it. ### `mcporter config add [target]` - Persists a server into the writable config file. Accepts both positional shortcuts (`mcporter config add sentry https://mcp.sentry.dev/mcp`) and flag-driven definitions: - `--transport http|sse|stdio` - `--url` or `--command`/`--stdio` - `--env`, `--header`, `--token-cache-dir`, `--description`, `--tag`, `--client-name`, `--oauth-redirect-url` - `--oauth-client-id`, `--oauth-client-secret-env`, `--oauth-token-endpoint-auth-method` for pre-registered OAuth clients. - `--copy-from importKind:name` to clone settings from an imported entry before editing. - `--dry-run` shows the JSON diff without writing, while `--persist ` overrides the destination file. ### `mcporter config remove ` - Removes the local definition. Names sourced exclusively from imports remain untouched until you copy them locally. ### `mcporter config import ` - Displays (and optionally copies) entries from editor-specific configs: - `cursor`: `.cursor/mcp.json` in the repo, falling back to `~/.config/Cursor/User/mcp.json` (or `%APPDATA%/Cursor/User` on Windows). - `claude-code`: `/.claude/settings.local.json`, `/.claude/settings.json`, `/.claude/mcp.json`, then `~/.claude/settings.json`, `~/.claude/mcp.json`, `~/.claude.json`. `settings.local.json` is meant for untracked per-developer overrides, while `settings.json` is the shared project config. - `claude-desktop`: platform-specific `Claude/claude_desktop_config.json` paths. - `codex`: `/.codex/config.toml`, then `~/.codex/config.toml`. - `windsurf`: Codeium’s Windsurf config under `%APPDATA%/Codeium/windsurf/mcp_config.json` or `~/.codeium/windsurf/mcp_config.json`. - `opencode`: Honors `OPENCODE_CONFIG` when set, then `/opencode.json(c)`, `OPENCODE_CONFIG_DIR/opencode.json(c)`, and finally `${XDG_CONFIG_HOME:-~/.config}/opencode/opencode.json(c)` (or `%APPDATA%/opencode/opencode.json(c)` on Windows). Both `.json` and `.jsonc` extensions are supported. - `vscode`: `Code/User/mcp.json` (stable + Insiders) inside the OS-appropriate config directory. - `--copy` writes selected entries into your local config; `--filter ` narrows the import list; `--path ` lets you point at bespoke locations. ### `mcporter config login ` / `logout` - Mirrors `mcporter auth`. `login` completes OAuth (or token provisioning) for either a named server or an ad-hoc URL. When a hosted MCP returns 401/403, mcporter automatically promotes that target to OAuth and re-runs the flow, matching the behavior documented in `docs/adhoc.md`. - `--no-browser` suppresses automatic browser launch and prints the authorization URL to stdout so it can be copied from a headless host. `--browser none` is accepted as a compatibility alias, and `MCPORTER_OAUTH_NO_BROWSER=1` / `true` / `yes` enables the same behavior by environment. - In `--json --no-browser` mode, stdout contains a JSON object with `authorizationUrl` and `redirectUrl`; diagnostics stay off stdout so scripts can parse the result. Treat emitted authorization URLs as sensitive operational output. - `logout` wipes the shared vault entry, legacy `~/.mcporter//` caches, and the custom `tokenCacheDir` when present. Pass `--all` to clear everything. ### `mcporter config doctor` - Early validator that checks for simple issues (e.g., OAuth entries missing cache paths). Future iterations will add fixes for Accept headers, duplicate imports, and more. ## Ad-hoc & Persistence - `--http-url` and `--stdio` flags live on `mcporter list|call|auth`, keeping `mcporter config` focused on persistent config files. Ad-hoc HTTP targets also accept repeatable `--header KEY=value` flags for private endpoints. - Names default to slugified hostnames or executable/script combos. Supply `--name` to improve reuse; mcporter uses that slug for OAuth caches even before persistence. - `--allow-http` is mandatory for cleartext endpoints so we never downgrade transport silently. - Add `--persist ` (defaulting to `config/mcporter.json` when omitted) to copy the ad-hoc definition into config. We reuse the same serializer as the import pipeline, so copying from Cursor → local config produces identical structure and preserves custom env/header fields. - When an ad-hoc HTTP server returns an OAuth challenge during `list`, `call`, or `auth`, the persisted entry is rewritten with `auth: "oauth"` so later commands use the cached OAuth path instead of retrying unauthenticated HTTP. - `--env KEY=VAL` entries merge with existing `env` dictionaries if you later persist the same server; nothing is lost when you alternate between CLI flags and JSON edits. - `--header KEY=VAL` entries merge into the persisted HTTP `headers` object when used with `--persist`; values support the same `$env:VAR`, `${VAR}`, and `${VAR:-fallback}` placeholders as config-file headers. ## HTTP Compatibility HTTP MCP servers normally use Node's built-in `fetch` through the upstream MCP SDK. If a provider rejects that stack but accepts plain Node `https.request` traffic, set `httpFetch: "node-http1"` on the server entry to force an HTTP/1.1 fetch implementation for Streamable HTTP and SSE POST requests: ```jsonc { "mcpServers": { "sunsama": { "baseUrl": "https://api.sunsama.com/mcp", "headers": { "Authorization": "Bearer ${SUNSAMA_TOKEN}" }, "httpFetch": "node-http1", }, }, } ``` The Sunsama endpoint is auto-detected and uses this compatibility path by default. ## JSON Schema for IDE Support mcporter provides a JSON Schema for config file validation and autocompletion. Add the `$schema` property to your config file: ```jsonc { "$schema": "https://raw.githubusercontent.com/steipete/mcporter/main/mcporter.schema.json", "mcpServers": { ... } } ``` For local development, you can reference the schema from the repo root: ```jsonc { "$schema": "../mcporter.schema.json", "mcpServers": { ... } } ``` The schema is auto-generated from the Zod validation schemas using `pnpm generate:schema`. ## Schema Reference Top-level structure: | Key | Type | Description | | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------- | | `mcpServers` | object | Map of server names → definitions. Required even if empty. | | `imports` | string[] | Optional list of import kinds. Empty array disables imports entirely; omitting the key falls back to the default list. | Server definition fields (subset of what `RawEntrySchema` accepts): | Field | Description | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `description` | Free-form summary printed by `mcporter list`/`config list`. | | `baseUrl` / `url` / `serverUrl` | HTTPS or HTTP endpoint. `http://` requires `--allow-http` in ad-hoc mode but works in config if you explicitly set it. | | `command` / `args` | Stdio executable definition (string or array). Arrays are preferred because they avoid shell quoting issues. | | `cwd` | Working directory for stdio servers. A leading `~` is expanded to `$HOME`; relative paths resolve against the config file directory. Defaults to the config file directory when omitted. | | `env` | Key/value pairs applied when launching stdio commands. Supports `${VAR}` interpolation and `${VAR:-fallback}` defaults. Existing process env values win over fallbacks. | | `headers` | Request headers for HTTP/SSE transports. Values can reference `$env:VAR` or `${VAR}` placeholders, which must be set at runtime or mcporter aborts with a helpful error. | | `auth` | Recognizes `oauth` and `refreshable_bearer`. `oauth` runs the browser/client OAuth flow for HTTP transports; `refreshable_bearer` refreshes cached bearer tokens non-interactively before connecting. | | `tokenCacheDir` | Directory for OAuth tokens and schema caches; still honored, but mcporter now keeps a centralized vault in `~/.mcporter/credentials.json` or `$XDG_DATA_HOME/mcporter/credentials.json` when set (legacy per-server caches are auto-migrated). Supports `~` expansion. | | `clientName` | Optional identifier some servers use for telemetry/audience segmentation. | | `oauthClientId` | Pre-registered OAuth client id for providers that do not support dynamic client registration. | | `oauthClientSecretEnv` | Environment variable containing the OAuth client secret. Prefer this over committing `oauthClientSecret` directly. | | `oauthTokenEndpointAuthMethod` | Optional token endpoint auth method override, for example `client_secret_post` when the provider requires client credentials in the token request body. | | `oauthRedirectUrl` | Override the default localhost callback. Required for many pre-registered OAuth apps because the provider must allowlist the exact redirect URI. Also useful when tunneling OAuth through Codespaces or remote dev boxes. | | `oauthScope` | Optional explicit OAuth scope string. If omitted, mcporter lets the MCP SDK derive scope from server/auth metadata. Use this as an escape hatch for providers that require explicit scopes but don’t publish `scopes_supported`. | | `oauthCommand.args` | For STDIO servers that ship a custom auth subcommand (e.g., Gmail MCP). mcporter will spawn the stdio command with these args when you run `mcporter auth `, so you don’t need to call `npx ... auth` manually. | | `refresh` | Explicit token refresh settings for `auth: "refreshable_bearer"`. Supports `tokenEndpoint`, `clientIdEnv`, `clientSecretEnv`, `clientAuthMethod`, `refreshSkewSeconds`, and `accessTokenEnv` (plus snake_case aliases). | | `allowedTools` / `allowed_tools` | Optional exact-name allowlist. Only listed tools appear in `mcporter list` and can be called. An empty array blocks all tools. Cannot be combined with `blockedTools`. | | `blockedTools` / `blocked_tools` | Optional exact-name blocklist. Listed tools are hidden from `mcporter list` and rejected by `mcporter call`. Cannot be combined with `allowedTools`. | mcporter normalizes headers to include `Accept: application/json, text/event-stream` automatically, matching the runtime’s streaming expectations. String-valued config fields support `${VAR}` and `${VAR:-fallback}` placeholders. Secret-bearing `headers`, `env`, and bearer-token placeholders are preserved in `config get`/`config list` output and resolved only when the transport runs; `*Env` fields name environment variables and are not expanded. Top-level `daemonIdleTimeoutMs` (or `daemon_idle_timeout_ms`) shuts down the keep-alive daemon after that many milliseconds without daemon requests. Per-server `lifecycle.idleTimeoutMs` still controls when individual keep-alive transports are closed. ### Refreshable Bearer Tokens Use `auth: "refreshable_bearer"` when you already seeded OAuth tokens with `mcporter vault set ` or `tokenCacheDir`, and the server should receive only a fresh bearer token at runtime. HTTP servers get `Authorization: Bearer ` when no authorization header is already configured. STDIO servers require `refresh.accessTokenEnv`; mcporter refreshes before spawning the process and injects that env var with the raw access token. ```json { "mcpServers": { "example": { "command": "uvx", "args": ["example-mcp-server"], "auth": "refreshable_bearer", "refresh": { "tokenEndpoint": "https://api.example.com/oauth/token", "clientIdEnv": "EXAMPLE_CLIENT_ID", "clientSecretEnv": "EXAMPLE_CLIENT_SECRET", "clientAuthMethod": "client_secret_basic", "refreshSkewSeconds": 300, "accessTokenEnv": "EXAMPLE_ACCESS_TOKEN" } } } } ``` For keep-alive stdio servers, refresh happens before process start. If that process cannot read updated credentials after startup, use `lifecycle: "ephemeral"` or restart the daemon before the injected token expires. ## Imports & Conflict Resolution - `pathsForImport(kind, rootDir)` determines every candidate path. mcporter searches the repo first, then user-level directories, and stops at the first file that parses. - Entries pulled from imports are treated as read-only snapshots. The merge process keeps the first definition for each name; later sources with the same name are skipped until you override locally. - To copy an imported entry, either run `mcporter config import --copy --filter name` or use `mcporter config add name --copy-from kind:name`. The copy operation writes through the same JSON normalization stack, so the resulting file matches our schema even if the source format was TOML (Codex) or legacy JSON shapes (`servers` vs `mcpServers`). ## Project vs. Machine Layers - Keep `config/mcporter.json` under version control. Encourage contributors to add sensitive data via env vars (`${LINEAR_API_KEY}`) rather than inline secrets. - For pre-registered OAuth apps, store the public `oauthClientId` in config and point `oauthClientSecretEnv` at a local environment variable. `oauthClientSecret` is supported for private machine-local configs but should not be committed. - For headless deployments that already have OAuth credentials, run `mcporter vault set --tokens-file ` or `mcporter vault set --stdin` with a JSON payload shaped like `{ "tokens": { ... }, "clientInfo": { ... } }`. This lets mcporter compute the vault key from the resolved server definition instead of duplicating that internal format in scripts. - Machine-specific additions can live in `~/.mcporter/local.json` or `$XDG_CONFIG_HOME/mcporter/local.json`; point `mcporter config --config ~/.mcporter/local.json add ...` there when you prefer not to touch the repo. Since the runtime only watches one config at a time, CI jobs should always pass `--config config/mcporter.json` (or run from the repo root) for deterministic behavior. - OAuth tokens, cached server metadata, and generated CLIs should remain outside the repo (`~/.mcporter/...` or the matching `XDG_*_HOME/mcporter/...`, plus `dist/`). ## Validation & Troubleshooting - `mcporter list --http-url ...` refuses to auto-run OAuth to keep listing commands quick; use `mcporter config login ...` or `mcporter auth ...` to finish credential setup. - When env placeholders are missing, commands fail fast with the exact variable name. Add the variable or wrap it in `${VAR:-fallback}` to provide defaults. - Use `mcporter config get --show-source` (planned flag) to confirm whether a server came from an import. If a teammate’s Cursor config keeps overriding your local entry, reorder the `imports` array to move Cursor later or set it to `[]` to disable imports entirely. - `docs/adhoc.md` covers deeper debugging, including tmux workflows and OAuth promotion logs. ## Outstanding Coverage Items - Describe how `--persist` writes through the same import merge pipeline (especially once `mcporter config add --copy-from` ships) so users know exactly which file changes. - Call out that `--allow-http` remains required for cleartext URLs even in config mutations, and reiterate that `--env KEY=VAL` merges with on-disk env blocks rather than replacing them entirely. - Clarify and illustrate the automatic OAuth promotion path for ad-hoc HTTP entries in both this doc and future `mcporter config login` help output. - Flesh out `mcporter config doctor` once the validator is implemented so we can show real output samples and suggested fixes.