Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [0.15.1] - Unreleased

- Update Rolldown to 1.2.14 for generated CLI bundling, preserving Node 24 support and the 48-hour dependency cooldown.
- Add opt-in OAuth secret encryption with `MCPORTER_VAULT_PASSWORD`, a required policy, persistent encryption markers, migration on write, and vault/token-cache diagnostics. Older binaries must use a separate state directory after opt-in; see the downgrade instructions. Thanks @alexminza (#425, #408).

## [0.15.0] - 2026-10-16

**Highlights:** Keep healthy MCP servers available when another server fails, accept inline ad-hoc flags, add runnable MCP examples, and rehearse releases without publishing.
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,16 @@ The first command prints the server's TypeScript-style tool signatures. The seco

## Core workflows

| Goal | Command or API | Details |
| ----------------------------------------- | ------------------------------------ | ------------------------------------------ |
| Discover servers and tools | `mcporter list` | [CLI reference](docs/cli-reference.md) |
| Call tools and read resources | `mcporter call`, `mcporter resource` | [Call syntax](docs/call-syntax.md) |
| Connect to a one-off URL or stdio command | `--http-url`, `--stdio` | [Ad-hoc servers](docs/adhoc.md) |
| Complete or seed OAuth | `mcporter auth`, `mcporter vault` | [Configuration](docs/config.md) |
| Generate a focused CLI | `mcporter generate-cli` | [CLI generator](docs/cli-generator.md) |
| Generate TypeScript types or clients | `mcporter emit-ts` | [Typed clients](docs/emit-ts.md) |
| Capture a reproducible MCP session | `mcporter record`, `mcporter replay` | [Record and replay](docs/record-replay.md) |
| Goal | Command or API | Details |
| ----------------------------------------- | ------------------------------------ | --------------------------------------------------- |
| Discover servers and tools | `mcporter list` | [CLI reference](docs/cli-reference.md) |
| Call tools and read resources | `mcporter call`, `mcporter resource` | [Call syntax](docs/call-syntax.md) |
| Connect to a one-off URL or stdio command | `--http-url`, `--stdio` | [Ad-hoc servers](docs/adhoc.md) |
| Complete or seed OAuth | `mcporter auth`, `mcporter vault` | [Configuration](docs/config.md) |
| Encrypt the OAuth vault at rest | `MCPORTER_VAULT_PASSWORD` | [Vault Encryption](docs/config.md#vault-encryption) |
| Generate a focused CLI | `mcporter generate-cli` | [CLI generator](docs/cli-generator.md) |
| Generate TypeScript types or clients | `mcporter emit-ts` | [Typed clients](docs/emit-ts.md) |
| Capture a reproducible MCP session | `mcporter record`, `mcporter replay` | [Record and replay](docs/record-replay.md) |

Human-readable output goes to stdout by default. Use JSON output when another program or agent needs a stable result, and consult `mcporter <command> --help` for the flags supported by that command.

Expand Down
33 changes: 28 additions & 5 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ mcporter honors XDG Base Directory env vars for its own paths when they are expl
| 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/...` |
| data | `XDG_DATA_HOME` | `$XDG_DATA_HOME/mcporter/credentials.json` | `~/.mcporter/...` | — see [Vault Encryption](#vault-encryption) |
| cache | `XDG_CACHE_HOME` | `$XDG_CACHE_HOME/mcporter/<server>/schema.json` | `~/.mcporter/...` |
| state | `XDG_STATE_HOME` | `$XDG_STATE_HOME/mcporter/...` | `~/.mcporter/daemon` |

Expand Down Expand Up @@ -210,10 +210,11 @@ The schema is auto-generated from the Zod validation schemas using `pnpm generat

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. |
| 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. |
| `oauthVaultEncryption` | string | `"optional"` (default) or `"required"`; see [Vault Encryption](#vault-encryption). `MCPORTER_VAULT_ENCRYPTION` overrides it. |

Server definition fields (subset of what `RawEntrySchema` accepts):

Expand Down Expand Up @@ -293,6 +294,28 @@ For keep-alive stdio servers, refresh happens before process start. If that proc
- 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/`).

## Vault Encryption

> **Before opting in:** upgrade and restart every CLI, daemon, and generated CLI that shares the vault or a `tokenCacheDir`. Older binaries do not enforce encryption: re-authentication can silently put plaintext credentials back on disk. A format marker cannot stop already-released code from doing that. Do not downgrade or mix those versions against an encrypted profile; use the isolated rollback procedure below.

The OAuth vault (`credentials.json`, see the XDG table above) is plaintext JSON by default. Set `MCPORTER_VAULT_PASSWORD` to store each secret value in it (`access_token`, `refresh_token`, `id_token`, `client_secret`, `registration_access_token`, the PKCE verifier and OAuth state) as an RFC 7516 JWE compact string in place: `PBES2-HS512+A256KW`, `A256GCM`, 8192 PBKDF2 iterations. The JSON document retains version 2 and gains an `encryption: { "version": 1 }` marker; server names, URLs, client IDs and timestamps stay readable, and any JOSE library can decrypt a value with the password (`jose` for Node needs `PBES2-HS512+A256KW` in `keyManagementAlgorithms`). The password must be generated, not chosen: with 8192 key-derivation iterations its randomness is the whole protection against an offline guess at a copied file. It must be at least 32 characters; `openssl rand -base64 32` produces a suitable value.

- Existing plaintext values are read as before and encrypted the next time a login is added or renewed (a token refresh, `mcporter auth`, `mcporter vault set`); until then they stay plain. `mcporter config doctor` shows how many values are still plain, in the vault and in each configured `tokenCacheDir`.
- Once marked, the vault requires the password even after its last entry is cleared. A `tokenCacheDir` records the same choice in `encryption.json`. Supporting versions refuse unknown encryption-marker versions without repairing or overwriting them. Sealed values from unmarked stores are also recognized and refused without the password.
- There is no password rotation: with a new password the stored values no longer open; run `mcporter vault clear <server>` and log in again. Restart `mcporter daemon` after changing these variables.
- `oauthVaultEncryption: "required"` in `mcporter.json` (or `MCPORTER_VAULT_ENCRYPTION=required`, which overrides the config key) refuses every vault operation while the password is unset, so a cleared environment cannot write plaintext values. Default `optional`. The config key applies to servers defined in config; ad-hoc servers (`mcporter auth <url>`) follow `MCPORTER_VAULT_ENCRYPTION` only. A CLI generated with `mcporter generate-cli` embeds the policy of the config file or definition it was generated from, and generating under `required` needs the password set, because tool discovery goes through the vault.
- Tokens saved by older versions under `~/.mcporter/<server>/` are copied into the vault the first time the server is used, as before. With the password set the old plaintext files are removed after the copy; without it they stay.
- Servers with a `tokenCacheDir` get the same treatment: the secret values in `tokens.json`, `client.json`, `code_verifier.txt` and `state.txt` are encrypted, and the first write with the password set (any write, including the discovery metadata) encrypts every one of those files that is still plain; the other files there are not secrets. Without the password, a directory that holds encrypted values refuses every write and delete and is never modified, as the vault is. The `required` policy refuses the directory too, and a plaintext directory never hides a vault that needs the password.
- Stdio MCP servers, whether started directly or by the keep-alive daemon, and the OAuth auth helper are started without `MCPORTER_VAULT_PASSWORD` and `MCPORTER_VAULT_ENCRYPTION` in their environment; everything else they inherit is unchanged, and a value set in a server's own `env` still applies.

### Downgrade and rollback

There is no in-place plaintext downgrade or password rotation command. Unsetting the password is not a rollback. The markers protect supporting versions, but old binaries ignore them (and an unknown top-level vault version is treated as corrupt and rewritten by those binaries). Neither the marker nor `required` can impose a policy on code released before this feature.

To run an older version safely, stop the daemon and all consumers of the encrypted profile, preserve that profile and its password, and run the older binary with a **different `HOME`, `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_CACHE_HOME`, and `XDG_STATE_HOME`**. Use a fresh config in that profile; remove or change every explicit `tokenCacheDir` so it cannot point back into the encrypted profile. Authenticate again there, accepting that the old version stores those new credentials in plaintext. Do not copy encrypted credentials into the old profile. To roll forward, stop the old consumers, restore the original environment and password, and restart the supporting version against the preserved encrypted profile.

If an older consumer has already written plaintext into the encrypted profile, stop it, restore the supporting version and password, and trigger a credential write to seal the whole store again. Run `mcporter config doctor` to check for residue. Sealing cannot undo exposure in backups or copies made during that window; revoke affected credentials at their providers if needed.

## 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.
Expand Down
2 changes: 1 addition & 1 deletion docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,4 @@ The `list` command prints every MCP server discovered across your configs (Curso
- `brew uninstall steipete/tap/mcporter`
- Standalone binary: delete the file you copied onto `$PATH`.

OAuth credentials use `~/.mcporter/credentials.json` or `$XDG_DATA_HOME/mcporter/credentials.json`; cached schemas use `~/.mcporter/<server>/` or `$XDG_CACHE_HOME/mcporter/<server>/`. Custom `tokenCacheDir` settings can place them elsewhere. Run `mcporter config logout <server>` to clear that server's credentials while preserving unrelated files. See [configuration paths](config.md#config-resolution-order) before removing any remaining config or cache files.
OAuth credentials use `~/.mcporter/credentials.json` or `$XDG_DATA_HOME/mcporter/credentials.json`; cached schemas use `~/.mcporter/<server>/` or `$XDG_CACHE_HOME/mcporter/<server>/`. Custom `tokenCacheDir` settings can place them elsewhere. With `MCPORTER_VAULT_PASSWORD` set, the secret values in these files are encrypted; see [Vault Encryption](config.md#vault-encryption). Run `mcporter config logout <server>` to clear that server's credentials while preserving unrelated files. See [configuration paths](config.md#config-resolution-order) before removing any remaining config or cache files.
2 changes: 1 addition & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ npm install mcporter

## 3. OAuth Tokens

- Tokens are saved in the shared vault under `~/.mcporter/credentials.json` by default, or `$XDG_DATA_HOME/mcporter/credentials.json` when `XDG_DATA_HOME` is set.
- Tokens are saved in the shared vault under `~/.mcporter/credentials.json` by default, or `$XDG_DATA_HOME/mcporter/credentials.json` when `XDG_DATA_HOME` is set, with secret values encrypted when `MCPORTER_VAULT_PASSWORD` is set ([Vault Encryption](config.md#vault-encryption)).
- To force a fresh login for one server, run `mcporter auth <server> --reset`. This clears its recognized credentials without deleting unrelated files or other servers' vault entries.
- Custom `token_cache_dir` entries in `mcporter.json` continue to work as explicit overrides.

Expand Down
5 changes: 5 additions & 0 deletions mcporter.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,11 @@
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"oauthVaultEncryption": {
"description": "Policy for the shared OAuth vault: \"required\" refuses every vault operation unless MCPORTER_VAULT_PASSWORD is set. MCPORTER_VAULT_ENCRYPTION overrides this key.",
"type": "string",
"enum": ["optional", "required"]
},
"imports": {
"description": "Editor configurations to import servers from. Omit to use defaults, or set to [] to disable imports",
"type": "array",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@
"acorn": "^8.19.0",
"commander": "^15.0.0",
"es-toolkit": "^1.52.0",
"jose": "^6.2.12",
"jsonc-parser": "^3.3.1",
"ora": "^9.4.1",
"rolldown": "1.2.13",
"rolldown": "1.2.14",
"zod": "^4.6.7"
},
"devDependencies": {
Expand Down
Loading
Loading